leetcode 322 零钱兑换
bobo老师您好,我在做leetcode322题的时候,有两个问题一直困扰着我,我也尝试了打印出来和画图,还是想不通。老师能帮我解答解答吗。这道题我采用的是递归+记忆化搜索的思路。
我的问题
- 问题一:这种涉及到求最大值最小值的递归,采用print方法适合来debug吗?如果适合,print应该放在哪些位置,需要打印出哪些值,能够让我更好的看清程序的运行过程呢?
- 问题二:下面的版本一中的
res = min(res, self.dfs(coins, amount-coins[i], count+1))和版本二中的res = min(res, 1+self.dfs(coins, amount-coins[i]))我分析起来觉得他们的效果一样特别是1+self.dfs()和递归过程中count+1最后再return count,我的理解这两种最后返回的值应该是一样的。但是为什么却得到不同的结果呢?
我的代码
- 版本一: 递归+记忆化搜索的方法(此方法答案错误了)
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
self.memo = [-1]*(amount+1)
res = self.dfs(coins, amount, 0)
if res == float('inf'):
return -1
else:
return res
def dfs(self, coins, amount, count):
if amount < 0:
return float('inf')
if amount == 0:
return count
if self.memo[amount] != -1:
return self.memo[amount]
res = float('inf')
for i in range(len(coins)):
res = min(res, self.dfs(coins, amount-coins[i], count+1))
self.memo[amount] = res
return res
- 版本二:递归+记忆化搜索的方法(accepted)
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
self.memo = [-1] * (amount+1)
res = self.dfs(coins, amount)
if res == float('inf'):
return -1
else:
return res
def dfs(self, coins, amount):
if amount == 0:
return 0
if amount < 0:
return float('inf')
if self.memo[amount] != -1:
return self.memo[amount]
res = float('inf')
for i in range(len(coins)):
res = min(res, 1+self.dfs(coins, amount-coins[i]))
self.memo[amount] = res
return res
- 我的print方法(但是通过这样设置print我还是不能很好的看出程序运行过程)
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
if amount == 0:
return 0
res = self.dfs(coins, amount, 0)
if res == float('inf'):
return -1
else:
return res
def dfs(self, coins, amount, depth):
if amount == 0:
return 0
if amount < 0:
return float('inf')
res = float('inf')
for i in range(len(coins)):
print(self.gen_depth(depth), res)
res = min(res, 1+self.dfs(coins, amount-coins[i], depth+1))
return res
def gen_depth(self, depth):
string = ''
for i in range(depth):
string += '-'
return string
正在回答
写递归函数,最最最最最最最最最最最最最重要的,就是递归函数的语义。(其实写循环也一样,但是这一点在递归函数上表现得最明显。)这其实也是我在这个课程中一直强调的一点。
你给出的第二个递归函数,语义很清晰,dfs(coins, amount) 的意思是,就是使用 coins 这些面值的硬币,组成 amount 这么多钱,dfs 的返回值是最小需要多少枚硬币。
你给出的第一个递归函数,dfs(coins, amount, count),参数我没有疑问,使用 coins 这些面值的硬币,组成 amount 这么多钱,count 是现在已经使用了多少枚硬币。那关键是,这个递归函数的返回值是什么意思?
严格来讲,在你给出这个具体的定义之前,我们讨论你的代码都没有意义。因为具体的代码是建立在这个定义的基础上的。有了定义,我们才能看你的代码是否正确(即是否遵守了这个定义。)
===============
现在我假设一个定义,你的 dfs 的返回值也是指,组成 amount 这么多钱,至少需要多少硬币。(我怀疑你在写这个函数的时候,就是这么认为的,所以你返回 dfs(coins, amount, 0) 就是你认为的解)
那么,在这个顶一下,if amount == 0: return count 这句话就有问题。因为此时 count 不为 0,我们组成 0 元钱,怎么可能用 count 个硬币?(再反观第二个递归函数:if amount == 0: return 0 到底这里为什么 return 0?不是因为到了 0 一定 return 0,而是因为语义!语义!语义!我们要想获得 0 元钱,只能用 0 个硬币。)
同理的,你可以随便用一个用例,看你的 memo 里都记录了什么。以 题目中的第一个测试用例 coins = [1, 2, 5], amount = 11 为例,你可以很容易看到,你的程序里,memo[1] = 11(一块钱怎么可能用 11 枚硬币)?memo[2] =10 (两块钱怎么可能用 10 枚硬币?等等等等)
=============
btw,我的调试方法就是看 在 self.memo[amount] = res 的时候,每一个 amount 记录的 res 到底是什么?它合不合理?如果不合理,可能是哪里有问题。你这样打印一句就可以。
self.memo[amount] = res print(amount, res) return res
但其实我相信你会这样调试,你之所以没有调试明白,应该不是调试方法的问题,我相信你看到了自己的 memo 数组里都记录了什么,你甚至跟踪了他为什么会记录这些值。但是,你没有退一步仔细想清楚,自己的 memo 数组到底是什么意思,记录这样的结果是不是正确的?而是陷入了语句的逻辑中(多一个参数,少一个参数,把这个运算放在这里或者这里。)再次强调,没有定义清楚变量,函数的语义,逻辑没有意义。逻辑是为这些语义服务的,而非反过来。
这个错误很容易犯。比如我在这个课程中,讲的二路快排或者三路快排,再或者二分查找,很多同学在听我的课程之前,边界的地方总是搞不清楚,究其原因,不是逻辑不严谨,而是没有在写程序前就严格定义清楚,每一个变量,到底是什么意思。一旦定义清楚了,在逻辑书写中,就要严格遵守这个定义。如果程序出了问题,debug 的过程,其实就是看,在每一步,变量的值是不是反映了这个定义。如果不是,逻辑哪里出了问题。
这个问题非常非常好。搞明白什么是正确的很重要,但很多时,候搞明白自己为什么错了更重要。
继续加油!:)
恭喜解决一个难题,获得1积分~
来为老师/同学的回答评分吧
0 星