分治策略
分而治之:
- 将问题分为若干更小规模的部分;
- 通过解决每一个小规模部分问题,将结果汇总得到原问题的解。
递归三定律:
- 基本结束条件,解决最小规模问题
- 缩小规模,向基本结束条件演进
- 调用自身来解决已缩小规模的相同问题
总结: 问题解决依赖于若干缩小了规模的问题汇总得到原问题的解。
应用 排序、查找、遍历、求值等。
优化问题: 算法都是为了找到某些问题的最优解。
LeetCode322. 零钱兑换
这是一个经典的案例,找零兑换问题。
贪心策略
解决这类问题最直观的就是贪心算法。用尽量多的数量有余额的,再到下一最大面值的货币,直到最后。
因为我们每次都试图解决问题的尽量大的一部分对应到兑换零钱的问题,就是每次以最多数量的最大面值货币来迅速减少找零面值。
贪心策略解决找零兑换问题,在美元或者其他货币的硬币体系下表现尚好。例如:在Elbonia体系中,存在21面值货币;
以63为例;63 = 25*2 + 10*1 + 1*3(美元硬币);
但实际上还有 63 =21*3。
贪心策略失效了。贪心策略是否有效依赖于具体的硬币体系。
美元面值:1美元、5美元、10美元、20美元、50美元和100美元纸币,另有1美分、5美分、10美分、25美分、50美分、1美元硬币。
找零兑换问题:递归解法及其改进
递归解法
货币体系:[1, 5, 10, 25, 50]
基本结束条件:
- 需要兑换找零,其面值正好等于某种硬币;
- 减小问题规模;
coinChange = min(
1 + coinChange(original - 1),
1 + coinChange(original - 5),
1 + coinChange(original - 10),
1 + coinChange(original - 20)
)
递归解法代码:
def coinChange(coinlist, change):
minCoins = change
if change in coinlist:
return 1
else:
for i in [c for c in coinlist if c <= change]:
numCoins = 1 + coinChange(coinlist, change-i)
if numCoins < minCoins:
minCoins = numCoins
return minCoins
print(coinChange([1,5,10,25],63))
输出结果:6种
递归虽然可以解决问题,但是极其低效。
可以用time模块来看一下时间。这是因为重复计算太多。
递归改进解法
改进关键:消除重复计算。
我们可以用一个表将计算过的中间结果保存起来,在计算之前查表看看是否已经计算过。
在递归调用之前,先查找表中是否已有部分找零的最优解;
如果有,直接最优解而不进行递归调用;
如果没有,才进行递归调用。
def coinChange(coinlist, change, knowResults):
minCoins = change
if change in coinlist:
knowResults[change] = 1
return 1
elif knowResults[change] > 0:
return knowResults[change]
else:
for i in [c for c in coinlist if c <= change]:
numCoins = 1 + coinChange(coinlist, change-i, knowResults)
if numCoins < minCoins:
minCoins = numCoins
knowResults[change] = minCoins
return minCoins
print(coinChange([1,5,10,25],63,[0]*64))
改进之后,极大减少了递归调用次数;63的兑换递归调用是改进前的三十万分之一。