Coin Change 2 - Dynamic Programming Unbounded Knapsack - Leetcode 518 - Python

แชร์
ฝัง
  • เผยแพร่เมื่อ 27 ต.ค. 2024

ความคิดเห็น • 99

  • @NeetCode
    @NeetCode  3 ปีที่แล้ว +56

    This one turned out pretty long, but I hope the timestamps are helpful. Also, the code for all 3 solutions is in the description. 😃

    • @jay-rathod-01
      @jay-rathod-01 3 ปีที่แล้ว +7

      turned out pretty long because your explaination was amazing. keep it up.

    • @kevinwang7811
      @kevinwang7811 3 ปีที่แล้ว

      Great explanation!
      BTW, do you have any idea how we may solve the same problem if we're provided only a limited number of each coin? For example, amount = 5, coins = [1, 2, 5], count = [1, 2, 3] (1 coin of value "1", 2 coins of value "2", 3 coins of value "5")?

    • @wraymond4326
      @wraymond4326 2 ปีที่แล้ว

      Excellent

    • @ahmed_nyc
      @ahmed_nyc 2 ปีที่แล้ว

      If I get into Google, I will buy you a beer, and I'm Muslim.

    • @gustaveloial3933
      @gustaveloial3933 ปีที่แล้ว

      @@kevinwang7811 assuming that coins is now a dictionary {denomination:count, ...} ex {2:3, 3:1, 5:2} I came up with the code below. for *each* new coin (denomination AND count), I update the table with itself, shifted by the amount.
      def changeIterBounded(coins, target):
      # Seeding the table of ways: No (0) ways to sum up to any amount with no denomination...
      gTable = [0 for _ in range(target +1)]
      # ... except for an amount of 0 for which there is only one (1) way
      gTable[0] = 1
      # Populating the table with each of the denominations (and the amount available)
      for theCoinVal, theCoinCnt in coins.items():
      # for each denomination shift the previous table by the new amount (denomination and count)
      theNewWay = [0] * (len(gTable))
      for i in range(1, theCoinCnt +1):
      # Shift
      theTempWay = ([0] * theCoinVal * i) + gTable
      # Truncate the length to the original target
      theTempWay = theTempWay[:len(gTable)]
      # consolidate
      for idx in range(len(gTable)):
      theNewWay[idx] += theTempWay[idx]
      # update the original table with the newly found ways.
      for idx in range(len(gTable)):
      gTable[idx] = gTable[idx] + theNewWay[idx]
      return gTable[-1]

  • @pritam1366
    @pritam1366 3 ปีที่แล้ว +72

    From coin change to coin change 2 it's a big jump pretty big to be honest.. shit man

  • @zahaankhan6699
    @zahaankhan6699 2 ปีที่แล้ว +92

    i have it crazy that interviewers expect you to have never seen this problem and answer it in an hour.

    • @thatguy14713
      @thatguy14713 2 ปีที่แล้ว +5

      Once you learn the pattern its not to bad.

    • @robr4501
      @robr4501 2 ปีที่แล้ว +2

      what company is that? is it a junior role?

    • @art4eigen93
      @art4eigen93 2 ปีที่แล้ว +25

      You are getting one hour? Here got just 20 mins for these kinds of problems.

    • @gianni50725
      @gianni50725 2 ปีที่แล้ว +2

      This one isn’t so bad. Other questions are much worse; at least you could arrive at this solution by yourself

    • @Randomisticful
      @Randomisticful ปีที่แล้ว +16

      And it is also very fun when you learn it. When you will get old, you will get to brag to your nephews and nieces how you learned the Knapsack problem and never ever used it directly.

  • @yashbhandari2964
    @yashbhandari2964 3 ปีที่แล้ว +112

    Hey, just as a heads up you can do an O(n) space dp solution using just one array:
    dp=[0]*(amount+1)
    dp[0] = 1
    for coin in coins:
    for i in range(1, amount+1):
    if i - coin >=0:
    dp[i] += dp[i-coin]
    return dp[-1]

    • @mukundyadav6913
      @mukundyadav6913 2 ปีที่แล้ว +2

      Hey! What does the base case dp[0] = 1 mean here? I mean, what does the number zero corresponding to 1 mean in this problem? that to make up 0 amount you need one coin?

    • @yashbhandari2964
      @yashbhandari2964 2 ปีที่แล้ว +17

      @@mukundyadav6913 It means that there is 1 way to make $0, which is by having 0 coins.

    • @mukundyadav6913
      @mukundyadav6913 2 ปีที่แล้ว +1

      @@yashbhandari2964 got it thanks!

    • @taroserigano6546
      @taroserigano6546 2 ปีที่แล้ว +2

      this is awesome!

    • @eskamobob8662
      @eskamobob8662 ปีที่แล้ว +2

      fucking wizardry. Makes complete sense seeing it but never would have thought of it.

  • @aakankshajaiswal9770
    @aakankshajaiswal9770 2 ปีที่แล้ว +6

    Thankyou for explaining using diagram. It was very helpful in understanding the space optimized approach.
    Below is my bottom up dp iterative solution in Python3:
    dp = [1] + [0]*amount

    for coin in coins:
    for amount_sum in range(coin, amount + 1):
    dp[amount_sum] += dp[amount_sum - coin]
    return dp[-1]

  • @edelweis303
    @edelweis303 ปีที่แล้ว +3

    Great explanation! the only input that I want to add is that for the recursive top-down solution, while it can produce the correct answer,
    the more accurate code that faithfully follows the top-down solution logic should be starting condition for "dfs(0, amount)", then change the base case in dfs(i, a) with following code:
    "if a == amount" -> "if a == 0"
    "if a > amount" -> "if a < 0"
    and change the general case code as follows:
    "cache[(i,a)] = dfs(i, a + coins[i]) + dfs(i + 1, a)" -> "cache[(i,a)] = dfs(i, a - coins[i]) + dfs(i + 1, a)"
    The complete code is as follows:
    class Solution(object):
    def change(self, amount, coins):
    """
    :type amount: int
    :type coins: List[int]
    :rtype: int
    """
    cache = {}
    def dfs(i, a):
    if a == 0:
    return 1
    if a < 0:
    return 0
    if i == len(coins):
    return 0
    if (i, a) in cache:
    return cache[(i,a)]
    cache[(i,a)] = dfs(i, a - coins[i]) + dfs(i + 1, a)
    return cache[(i,a)]
    return dfs(0, amount)
    the reason why we start with dfs(0, amount) is because in top down approach, we start with the final result value then slowly build our solution from the value amount to value 0(base case)

  • @abhishek.singh.chambial
    @abhishek.singh.chambial 2 ปีที่แล้ว +3

    Explained perfectly
    Just a minor tweak to use one array solution
    just update in-place instead of copying.
    class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
    cache = [0]*(amount + 1)
    cache[0]=1
    for coin in coins:
    for index in range(amount+1):
    if index-coin>=0:
    cache[index] += cache[index-coin]
    return cache[amount]

  • @rafaelbnn
    @rafaelbnn 4 หลายเดือนก่อน +1

    4:00 another way to look at it is to see that ALL the solutions that have a 1 will be in the first branch. That way you don't need to check again in the branch that starts with 2

  • @DJ-bo4pz
    @DJ-bo4pz ปีที่แล้ว +2

    Very nice explanation. But I would like to suggest you (from a pedagogical point of view), that it would be better if you had the amounts in the ascending order (left = 0 to right = 5) instead of the other way around, as l2r is the way most people think about arrays.

  • @jinny5025
    @jinny5025 3 ปีที่แล้ว +7

    Thanks for the video! Could you also cover 'Minimum Cost For Tickets' which is one of DP problems chosen from you?

  • @mangalegends
    @mangalegends 2 ปีที่แล้ว +1

    Thanks for the video! I had to draw out the recursion tree of your solution to really understand it. Definitely a more challenging problem

  • @errorbool531
    @errorbool531 2 ปีที่แล้ว +3

    class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
    dp = [1] + [0] * amount
    for c in coins:
    for i in range(c, amount + 1):
    dp[i] += dp[i - c]
    return dp[-1]

  • @DatascienceConcepts
    @DatascienceConcepts 2 ปีที่แล้ว +3

    Great explanation!
    You can further improve the space complexity (O(n)) by:
    #might need to do coins.sort()
    dp=[0]*(amount+1)
    dp[0]=1

    for coin in coins:
    for value in range(coin,amount+1):
    dp[value]+=dp[value-coin]
    return dp[-1]

  • @ztluo8824
    @ztluo8824 2 ปีที่แล้ว +7

    The code and the graph you draw is reversed direction, was hard to figure out already

  • @brandondavison3099
    @brandondavison3099 2 ปีที่แล้ว +5

    Why does his drawing show an array with three rows but when you run the code it's setup like this?
    [[1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

    • @noelnebu3524
      @noelnebu3524 8 หลายเดือนก่อน

      True I have the same question, but it doesn't really matter because when he is filling up the grid, he starts from len(coins) - 1 in the inner loop, so basically the last column of 0s is just added so the logic doesn't go out of bounds and 0 is being used from the extra column.

    • @fahim0404150
      @fahim0404150 4 หลายเดือนก่อน

      Think about it. if you don't add the additional column with all 0 values how are you going to add the next value to the 3rd column. Without the additional column the code will throw and error.

  • @IK-xk7ex
    @IK-xk7ex ปีที่แล้ว +3

    Hmm, the coins order have be ascending ordered (from lowest to highest), instead the DP solution won't pass test case where coins=[99,1]

  • @sekirandahamza1260
    @sekirandahamza1260 ปีที่แล้ว +3

    I fail to understand the table that is introduced at 8:05 .

  • @walidzein1
    @walidzein1 2 ปีที่แล้ว +12

    this should be named coin change 4 lol

  • @wilsonwang8641
    @wilsonwang8641 2 ปีที่แล้ว +7

    It will be better if you can explain the recursion solution code with the illustration together.

  • @sallaklamhayyen9876
    @sallaklamhayyen9876 7 หลายเดือนก่อน

    great explanation = thank you so much + please continue❤❤❤

  • @congr3342
    @congr3342 3 ปีที่แล้ว +1

    At 3:50, in the explanation that was given to prevent duplicates, what would happen if we continue going down the left-most 1 path? If the choices of {1,2,5} are still on the table, how is that logic going to prevent duplicates that start with a 1? For example, [1,1,1,2], [1,1,2,1], and [1,2,1,1]. Wouldn't those 3 sequences show up as distinct paths on that left-most sub-tree of 1? Perhaps I am misunderstanding it, any explanation would be helpful! Great video overall!

    • @oswald3042
      @oswald3042 3 ปีที่แล้ว +3

      Think of [1,1,1,1,1] as the leftmost chain. Now from each of the level in the leftmost chain, you can choose to have only 2 and 5 for the remaining sum (as for any level, 1 is already chosen as its leftmost child). Then all you can have going from left to right and top to bottom: [1,1,1,1,1], [1,1,1,2], [1,2,2],[5]. Basically you are allowed to go down the tree with choice of either coin of your own kind or to next available coin in sequence. From your question above [1,1,1,2], [1,1,2,1], and [1,2,1,1], only [1,1,1,2] is valid as [1,1,2,1] and [1,2,1,1] violate the rule of going back to previous coin "1" when you are at "2". hope it helps.

    • @celebezz
      @celebezz ปีที่แล้ว

      @@oswald3042 it does not help

    • @zacwong2151
      @zacwong2151 3 หลายเดือนก่อน

      correct me if im wrong, but [1,1,2,1], and [1,2,1,1] are not valid paths, because once you choose '2', you cannot go back and choose '1'

  • @nealsutaria2633
    @nealsutaria2633 12 วันที่ผ่านมา

    how important is it to do bottom up approach vs memorization for interviews?

  • @closingtheloop2593
    @closingtheloop2593 10 หลายเดือนก่อน +1

    Im struggling to understand the difference between DP and just recursion/iteration with memoization

  • @SamratDuttabdn
    @SamratDuttabdn 2 ปีที่แล้ว +1

    I have Amazon interviews this week. I hope I will crack it.

  • @illu1na
    @illu1na 9 วันที่ผ่านมา

    Why do we need nextDP? if you are setting nextdp[a] = dp[a] every sub iteration might as well just use dp in the first place?

  • @Blezerker
    @Blezerker ปีที่แล้ว

    This explanation was very difficult for me to understand, as it sounded a bit disorganized. Appreciate the upload regardless

  • @dollyvishwakarma2
    @dollyvishwakarma2 2 ปีที่แล้ว +6

    @NeetCode Can you please split this video and explain each of the solutions separately with a walkthrough of the code. It was difficult to follow this way.

  • @oscarchan5684
    @oscarchan5684 2 ปีที่แล้ว

    Thanks, it really helps me a lot!

  • @varcel4625
    @varcel4625 ปีที่แล้ว +2

    "And this path, will never have any(one)" 4:05😢

    • @kvtys
      @kvtys 8 วันที่ผ่านมา

      lol

  • @deathbombs
    @deathbombs ปีที่แล้ว

    Instead of dp[coin][amount] why can't we just use dp[amount] = number of ways for change?
    We can say we have all coins available, and it should give the answer still?

  • @DavidDLee
    @DavidDLee ปีที่แล้ว +1

    3:15 these rules don't guarantee no duplicates. Example: amount = 4 and going down the 1's branch, where 1's and 2's are allowed:
    0 -1-> 1 -1-> 2 -2-> 4 : 1+1+2 = 4
    0 -1-> 1 -2-> 3 -1-> 4 : 1+2+1 = 4
    And we did not touch the 2' branch.

    • @DavidDLee
      @DavidDLee 4 หลายเดือนก่อน

      It's applied recursively, so going down the top level 1 branch, when 2 is selected, 1s are no longer allowed.

  • @AnnieBox
    @AnnieBox 2 ปีที่แล้ว +2

    I'm living on your channel~~❥

  • @robr4501
    @robr4501 2 ปีที่แล้ว +7

    this question is medium?? wowwwwwww

    • @NeetCode
      @NeetCode  2 ปีที่แล้ว +9

      Agree, it's pretty hard for a medium

    • @robr4501
      @robr4501 2 ปีที่แล้ว

      @@NeetCode hey neet, can you tell me the difference between the 2 peoblems. coin change2 and combination sum ? the question is almost the same, but you took 2 different approach for each

    • @robr4501
      @robr4501 2 ปีที่แล้ว +1

      why is the base case for amount 0 is 1? since you can't use any coin, it should be 0 right?

  • @jasonthai9647
    @jasonthai9647 2 ปีที่แล้ว +1

    In the decision tree for the memoization solution, we're making at most 3 decisions but in the code you're only making 2 decisions. What am I missing here?

    • @chaitya6643
      @chaitya6643 2 ปีที่แล้ว +1

      The Decisions we are making is basically to pick the coin or to skip the coin... Therefore we only have two choice...

    • @muxa000
      @muxa000 2 ปีที่แล้ว +1

      You probably need to watch the Combination Sum problem explanation, hopefully it should make things clear

  • @devonchia5976
    @devonchia5976 2 หลายเดือนก่อน

    don't understand the skipping the current index in the memoization approach and it wasn't explained before the code. anyone knows the reason?

  • @Kyle-rf5mb
    @Kyle-rf5mb 11 หลายเดือนก่อน +2

    So i'm thick as fuck. Somebody remind me in the future to see if i ever get smart and can solve these problems. I've started my DSA journey so we will see

  • @director8656
    @director8656 3 ปีที่แล้ว

    great video btw

  • @fahidabdullah5637
    @fahidabdullah5637 ปีที่แล้ว +1

    Hi so I tried to do a memoization approach instead of a tabulation approach, where I made a function and in a for loop I went through each index, i, and made a subproblem with amount - coins[i], and starting at index i, for subproblem. I am able to complete this problem, but I realize my approach is very slow compared to a lot of the others I see. I was wondering, does anyone know why that is?

    • @Mihai-mb4ew
      @Mihai-mb4ew ปีที่แล้ว

      Hey, I just wrote something like that before watching this video. Seems like Neetcode is building from 0 to amount but we went from amount to 0. I don't know why it is so slow.

    • @Mihai-mb4ew
      @Mihai-mb4ew ปีที่แล้ว

      Replace your for loop with something like this: mem[total][start] = dfs(total - coins[start], start) + dfs(total, start + 1);
      My time went waaaay shorter. My intuition led me to write a for loop initially like you did too. I still do not grasp why Neetcode wrote it with only 2 recursive calls but it seems like it's closer to the DP matrix filling algo'.

    • @banana47859
      @banana47859 ปีที่แล้ว

      Done the same thing. The neetcode recursion approach doesnt match the diagram that he drew. It matches the tree diagram in the LeetCode's solution.

  • @Yougottacryforthis
    @Yougottacryforthis 6 หลายเดือนก่อน

    don't understand whats yourt mechanism to avoid dupes

  • @hoyinli7462
    @hoyinli7462 2 ปีที่แล้ว

    excellent!

  • @surajshah3590
    @surajshah3590 2 ปีที่แล้ว

    @NeetCode is there an easy way to extend the memoized code to return the actual combinations instead of the number of combinations?
    I have tried to return [coins[i]] when a==amount. The idea was to extend a list when traversing back up the tree. Didn't quite work.

  • @ooagabonjoaga2680
    @ooagabonjoaga2680 9 หลายเดือนก่อน

    here is the 1D c++ DP:
    int change(int amount, std::vector& coins) {
    std::vector mem(amount + 1, 0);
    mem[0] = 1;
    for(int coin: coins) {
    for(int i = 0; i = 0){
    mem[i] += mem[i-coin];
    }
    }
    }
    return mem[amount];
    }
    or,
    int change(int amount, std::vector& coins){
    std::vector mem(amount + 1, 0);
    mem[0] = 1; // 1 way to make 0 coins
    for(int coin : coins){
    for(int i = coin; i

  • @zachtheyek
    @zachtheyek 2 ปีที่แล้ว +14

    this was a terrible explanation lol: extremely scattershot, seemingly confusing yourself as you were explaining each step, addressing future problems halfway through the explanation for current problems, etc.
    It almost feels like you solved the problem 5 minutes before, and realized you had 10 minutes to make the video, but instead of putting it off for another day, you just rushed through it..
    Incredibly unlike your usual explanations, which are often concise, structured, and well thought out

    • @ThePacemaker45
      @ThePacemaker45 ปีที่แล้ว +2

      "terrible" is an insane stretch it was overall still very much intelligible. But yeah it was more scattershot than usual

    • @dankquan743
      @dankquan743 9 หลายเดือนก่อน

      disagree, maybe if you skipped coin change 1

    • @ibr0303
      @ibr0303 3 หลายเดือนก่อน

      Although the coding part was rushed through, the explanation is pretty solid and clear if you have solved and understood Coin Change 1.

  • @wij8044
    @wij8044 11 หลายเดือนก่อน

    function change(amount, coins) {
    const dp = new Array(amount + 1).fill(0);
    dp[0] = 1;
    for (const coin of coins) {
    for (let i = coin; i

  • @yuqian822
    @yuqian822 11 หลายเดือนก่อน +2

    I feel sympathetic when I first encountered this problem and even couldn't write a brute force 🥲