推荐网站:一个dp学习网站https://people.cs.clemson.edu/~bcdean/dp_practice/
最大子序列和
描述
对于一个数组(有正有负),找到连续的A[i]…A[j]使其和最大
可以采用动态规划的算法
以M[j]为到下标j时最大的子序列和,那么:
M[j] = max(M[j-1]+A[j], A[j])
显然从M[j-1]阶段到M[j]阶段,只有这两种情况,比较是继续加更合适,还是新开始一个序列更合适
最终遍历M找到最大的即可
时间复杂度为O(n)
leetcode 53为例,代码如下
代码
def maxSubArray(self, nums: List[int]) -> int:
if len(nums)==1:
return nums[0]
maxsum = [0]*len(nums)
maxsum[0] = nums[0]
for i in range(1,len(nums)):
if maxsum[i-1]+nums[i] >= nums[i]:
maxsum[i]= maxsum[i-1]+nums[i]
else:
maxsum[i] = nums[i]
result = -1
for n in maxsum:
if n > result:
result = n
return result
硬币找零
描述
给定一组硬币面值 v1…vn,和一个值C,一般若v1=1可以保证一定能成功找零,求得是找零需要的最少的钱币数
令 M[j]为兑齐钱币j所需的最少的硬币数,那么:
M[j] = min{M[j-vi]}+1
也就是当前状态还没有找齐j,再加一枚面值为vi的硬币可以凑齐,所以就是在现在的找齐(j-vi)面值的情况下再加一枚硬币,就可以找齐面值j
时间复杂度为O(n*C)
LeetCode 322,代码如下:
代码
def coinChange(self, coins: List[int], amount: int) -> int:
M = [amount+2]*(amount+2) #找齐j需要的最小硬币数
M[0] = 0
for i in range(1,amount+1):
for j in range(len(coins)):
if i>=coins[j] and M[i-coins[j]]!= amount+2 : # 注意条件
if M[i-coins[j]]+1 < M[i]:
M[i] = M[i-coins[j]]+1
if M[amount] == amount+2: # 无法找齐
return -1
else:
return M[amount]
Longest increasing subsequence
描述
在序列A[1…n]中,找出最长的递增子序列的长度,注意这个不一定是连续的
让L[j]为到下标j为止最长的递增子序列的长度,则
L[j] = max{L[i]}+1 其中 i<j 且 L[i]<L[j]
最后还是遍历一遍找最大的L[j]
时间复杂度为O(n^2),当然,这个也有O(nlogn)的解法
LeetCode 300
代码
def lengthOfLIS(self, nums: List[int]) -> int:
if len(nums)==0:
return 0
elif len(nums)==1:
return 1
L = [1]*len(nums) #初始化为1 因为当前位置至少自己可以构成一个序列
for j in range(1,len(nums)):
for i in range(j):
if nums[i]<nums[j]:
if L[i]+1 > L[j]:
L[j] = L[i]+1
res = -1
for n in L:
if n > res:
res = n
return res
boxStacking 叠放箱子
描述
这个题和longest increasing subsequence非常像
给定一组箱子的长宽高,分别是di,wi和hi,且di<wi,这样就避免了旋转箱子的问题
在叠箱子的时候,只有当wi<wj 且di<dj时才能把i叠放到j上
所以让H(j)为顶层为第j个箱子时的最高的高度,那么
H(j) = max{H(i)}+1 条件是 wi<wj 且di<dj
时间复杂度仍然是O(n^2)
build bridge
描述
如图,河两岸分别是n个城市,要在对应的城市间造桥,但是桥不能交叉,目标是建造最多的桥
这个问题与最长递增子序列非常类似
让x[i]为南岸城市对应的北岸城市的序号,比如x[1]=3, 可以用O(nlogn)时间内填充所有x[i],然后就可以找x[1]…x[n]的最长递增子序列了 (对岸是递增的就不会交叉)
未完待续