本篇文章根据labuladong的算法小抄汇总动态规划(游戏和贪心问题)的常见算法,采用python3实现
#dp函数:从左上角(0,0)到(i,j)都最小路径和为dp(grid,i,j)
def minPathSum(grid):
def dp(grid,i,j):
if (i == 0) and (j == 0):
return grid[0][0]
if (i < 0) or (j < 0):
return float("inf")
return min(
dp(grid,i-1,j),
dp(grid,i,j-1)
) + grid[i][j]
m = len(grid)
n = len(grid[0])
return dp(grid,m-1,n-1)
备忘录改进:
#时间O(MN),空间O(MN)
def minPathSum(grid):
def dp(grid,i,j):
if (i == 0) and (j == 0):
return grid[0][0]
if (i < 0) or (j < 0):
return float("inf")
if memo[i][j] != -1:
return memo[i][j]
memo[i][j] = min(dp(grid,i-1,j),dp(grid,i,j-1)) + grid[i][j]
return memo[i][j]
m = len(grid)
n = len(grid[0])
memo = [[-1 for i in range(n)] for j in range(m)]
return dp(grid,m-1,n-1)
dp数组:
#dp[i][j]:从(0,0)到(i,j)的最小路径和
def minPathSum(grid):
m = len(grid)
n = len(grid[0])
dp = [[0 for i in range(n)] for j in range(m)]
dp[0][0] = grid[0][0]
for i in range(1,m):
dp[i][0] = dp[i-1][0] + grid[i][0]
for j in range(1,n):
dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1,m):
for j in range(1,n):
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
return dp[m-1][n-1]
def findCheapestPrice(n,flights,src,dst,k):
#dp函数:从src出发,k步之内到达s的最小路径权重为dp(s,k)
def dp(s,k):
if s == src:
return 0
if k == 0:
return -1
res = float("inf")
if s in indegree:
for v in indegree[s]:
from_ = v[0]
price = v[1]
subProblem = dp(from,k-1)
if subProblem != -1:
res = min(res,subProblem + price)
return res if res != float("inf") else -1
k += 1
indegree = dict() #to -> [from,price]
for f in flights:
from_ = f[0]
to = f[1]
price = f[2]
if to in indegree:
indegree[to].append([from_,price])
else:
indegree[to] = [from_,price]
return dp(dst,k)
class Solution:
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
#dp[i][j]=x表示从src到i,j步之内的最便宜的价格(行表示n个城市对应索引,列表示k步之内)
k += 1
dp = [[float("inf") for i in range(k+1)] for j in range(n)]
for f,t,p in flights:
if f == src:
dp[t][1] = p
for x in range(2,k+1):
for f,t,p in flights:
dp[t][x] = min(dp[t][x],dp[t][x-1],dp[f][x-1]+p)
return dp[dst][k] if dp[dst][k] != float("inf") else -1
题目:给定k枚鸡蛋,1…n层楼。已知存在楼层f(0…n),任何高于f的楼层落下的鸡蛋都会碎,从f楼层或比它低的楼层落下的鸡蛋都不会破。如果鸡蛋碎了就不能用,如果没碎就可以用。请你计算并返回要确定f值的最小操作次数。
#时间O(K*N^2),空间O(KN)
def superEggDrop(K,N):
memo = dict()
def dp(K,N):
if K == 1:
return N
if N == 0:
return 0
if (K,N) in memo:
return memo[(K,N)]
res = float("inf")
for i in range(1,N+1):
res = min(res,max(dp(K,N-i),dp(K-1,i-1))+1)
memo[(K,N)] = res
return res
return dp(K,N)
#时间O(K,N,logN),空间O(KN)
class Solution:
def superEggDrop(self, k: int, n: int) -> int:
def dp(k,n):
if k == 1:
return n
if n == 0:
return 0
if (k,n) in memo:
return memo[(k,n)]
res = float("inf")
# 用二分搜索代替线性搜索
lo, hi = 1, n
while lo <= hi:
mid = (lo + hi) // 2
broken = dp(k - 1, mid - 1) # 碎
not_broken = dp(k, n - mid) # 没碎
# res = min(max(碎,没碎) + 1)
if broken > not_broken:
hi = mid - 1
res = min(res, broken + 1)
else:
lo = mid + 1
res = min(res, not_broken + 1)
memo[(k,n)] = res
return res
备忘录:
def rob(nums):
def dp(nums,start):
if start >= len(nums):
return 0
if memo[start] != -1:
return memo[start]
memo[start] = max(dp(nums,start+1),dp(nums,start+2)+nums[start])
return memo[start]
memo = [-1 for i in range(nums)]
return dp(nums,0)
dp数组
def rob(nums):
n = len(nums)
dp = [0 for i in range(n+2)]
for i in range(n-1,-1,-1):
dp[i] = max(dp[i+1],dp[i+2]+nums[i])
return dp[0]
状态压缩
def rob(nums):
n = len(nums)
dp_i_1 = 0
dp_i_2 = 0
for i in range(n-1,-1,-1):
dp_i = max(dp_i_1,dp_i_2+nums[i])
dp_i_2 = dp_i_1
dp_i_1 = dp_i
return dp_i
def rob(nums):
def robRange(nums,start,end):
n = len(nums)
dp_1 = 0
dp_2 = 0
for i in range(end,start-1,-1):
dp_i = max(dp_1,dp_2+nums[i])
dp_2 = dp_1
dp_1 = dp_i
return dp_i
n = len(nums)
if n == 1:
return nums[0]
return max(robRange(nums,0,n-2),robRange(nums,1,n-1))
class Solution:
def rob(self, root: TreeNode) -> int:
def dp(root):
if root is None:
return 0
if root in memo:
return memo.get(root)
do_it = root.val + (0 if root.left is None else dp(root.left.left)+dp(root.left.right))+(0 if root.right is None else dp(root.right.left) + dp(root.right.right))
not_do = dp(root.left) + dp(root.right)
res = max(do_it,not_do)
memo[root] = res
return res
memo = dict()
return dp(root)
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
暴力解法
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
M = len(needle)
N = len(haystack)
if M == 0:
return 0
if M > N:
return -1
for i in range(N-M+1):
j = 0
while j < M:
if haystack[i+j] != needle[j]:
break
else:
j += 1
if j == M:
return i
return -1
KMP算法
#dp[j][c]=next表示状态j碰到字符c转移到状态next
def KMP(pat):
M = len(pat)
dp = [[0 for i in range(256)] for j in range(M)]
dp[0][ord(pat[0])] = 1
X = 0
for j in range(1,M):
for c in range(256):
if ord(pat[j]) == c:
dp[j][c] = j + 1
else:
dp[j][c] = dp[X][c]
X = dp[X][ord(pat[j])]
def search(txt):
M = len(pat)
N = len(txt)
j = 0
for i in range(N):
j = dp[j][txt[i]]
if j == M:
return i-M+1
return -1
def maxA(n):
def dp(n,a_num,copy):
if n <= 0:
return a_num
if (n,a_num,copy) in memo:
return memo[(n,a_num,copy)]
memo[(n,a_num,copy)] = max(dp(n-1.a_num+1,copy),
dp(n-1,a_num+copy,copy),
dp(n-2,a_num,a_num)
)
return memo[(n,a_num,copy)]
memo = dict()
return dp(n,0,0)
def maxA(N):
dp = [0 for i in range(N+1)]
dp[0] = 0
for i in range(N+1):
#按A
dp[i] = dp[i-1] + 1
#第j个是ctrl+v
for j in range(2,i):
dp[i] = max(dp[i],dp[j-2]*(i-j+1))
return dp[N]
def stoneGame(piles):
n = len(piles)
dp = [[[0 for i in range(n)] for j in range(n)] for k in range(2)]
for i in range(n):
dp[i][i][0] = piles[i]
dp[i][i][1] = 0
for i in range(n-2,-1,-1):
for j in range(i+1,n):
left = piles[i] + dp[i+1][j][1]
right = piles[j] + dp[i][j-1][1]
if left > right:
dp[i][j][0] = left
dp[i][j][1] = dp[i+1][j][0]
else:
dp[i][j][0] = right
dp[i][j][1] = dp[i][j-1][0]
return dp[0][i-1][0] - dp[0][i-1][1]
def minInsertions(s):
n = len(s)
dp = [[0 for i in range(n)] for j in range(n)]
for i in range(n-2,-1,-1):
for j in range(i+1,n):
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1]
else:
dp[i][j] = min(dp[i+1][j],dp[i][j-1]) + 1
return dp[0][n-1]
给定一个数组,第i个元素是一支给定股票在第i天的价格,设计一个算法来计算你所能获取的最大利润。你最多可以完成k笔交易。注意:你不能同时参与多笔交易。
#原始版本
def maxProfits(prices):
n = len(prices)
dp = [[0 for i in range(2)] for j in range(n)]
dp[0][0] = 0
dp[0][1] = -prices[0]
for i in range(1,n):
dp[i][0] = max(dp[i-1][0],dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1],- prices[i])
return dp[n-1][0]
#空间复杂度优化版本
def maxProfits(prices):
n = len(prices)
dp_i_0 = 0
dp_i_1 = -prices[0]
for i in range(1,n):
dp_i_0 = max(dp_i_0,dp_i_1 + prices[i])
dp_i_1 = max(dp_i_1,- prices[i])
return dp_i_0
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp_i_0 = 0
dp_i_1 = -prices[0]
for i in range(1,n):
dp_i_0 = max(dp_i_0,dp_i_1 + prices[i])
dp_i_1 = max(dp_i_1,dp_i_0 - prices[i])
return dp_i_0
def maxProfit(prices):
n = len(prices)
dp = [[0 for i in range(2)] for j in range(n)]
dp[0][0] = 0
dp[0][1] = -prices[0]
for i in range(1,n):
dp[i][0] = max(dp[i-1][0],dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1],dp[i-2][0] - prices[i])
return dp[n-1][0]
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
dp = [[0 for i in range(2)] for j in range(n)]
dp[0][0] = 0
dp[0][1] = - prices[0]
for i in range(1,n):
dp[i][0] = max(dp[i-1][0],dp[i-1][1] + prices[i] - fee)
dp[i][1] = max(dp[i-1][1],dp[i-1][0] - prices[i])
return dp[n-1][0]
def maxProfit(prices):
max_k = 2
n = len(prices)
dp = [[[0 for i in range(max_k+1)] for j in range(3)] for k in range(n)]
for i in range(n):
for k in range(max_k,0,-1):#遍历顺序?
if i - 1 == -1:
dp[i][k][0] = 0
dp[i][k][1] = - prices[0] #不消耗交易次数?
continue
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i])
return dp[n-1][max_k][0]
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
n = len(prices)
if n == 0:
return 0
dp = [[[0 for i in range(2)] for j in range(k+1)] for h in range(n)]
for h in range(1,k+1):
dp[0][h][0] = 0
dp[0][h][1] = - prices[0]
for i in range(1,n):
for j in range(1,k+1):
dp[i][j][0] = max(dp[i-1][j][0],dp[i-1][j][1] + prices[i])
dp[i][j][1] = max(dp[i-1][j][1],dp[i-1][j-1][0] - prices[i])
return dp[n-1][k][0]
贪心算法是动态规划的一个特例,需要满足更多条件(贪心选择性质),但效率比动态规划高。
贪心选择性质:每一步都做出一个局部最优的选择,最终结果就是全局最优。
def intervalSchedule(intvs):
if len(intvs) == 0:
return 0
intvs.sort(key = lambda a : a[1])
count = 1
x_end = intvs[0][1]
for interval in intvs:
start = interval[0]
if start >= x_end:
count += 1
x_end = interval[1]
return count
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
n = len(intervals)
if n == 0:
return 0
intervals.sort(key=lambda a:a[1])
end = intervals[0][1]
count = 1
for interval in intervals:
if interval[0] >= end:
end = interval[1]
count += 1
return n - count
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
n = len(points)
if n == 0:
return 0
points.sort(key=lambda a:a[1])
count = 1
end = points[0][1]
for point in points:
if point[0] > end:
end = point[1]
count += 1
return count
#计算最多有几个重叠区间
def minMeetingRooms(meetings):
n = len(meetings)
begin = [0 for i in range(n)]
end = [0 for i in range(n)]
for i in range(n):
begin[i] = meetings[i][0]
end[i] = meetings[i][1]
begin.sort()
end.sort()
count = 0
res = 0
i = 0
j = 0
while (i < n) and (j < n):
if begin[i] < end[j]:
count += 1
i += 1
else:
count -= 1
j += 1
res = max(res,count)
return res
def videoStitching(clips,T):
n = len(clips)
clips.sort(key = lambda a:(a[0],-a[1]))
curEnd = 0
nextEnd = 0
count = 0
i = 0
while (i < n) and (clips[i][0] <= curEnd):
while (i < n) and (clips[i][0] <= curEnd):
nextEnd = max(nextEnd,clips[i][1])
i += 1
curEnd = nextEnd
count += 1
if curEnd >= T:
return count
return -1
def canJump(nums):
n = len(nums)
farthest = 0
for i in range(n-1):
farthest = max(farthest,i + nums[i])
if farthest <= i:
return False
return farthest >= n-1
动态规划:
def jump(nums):
def dp(nums,p):
n = len(nums)
if p >= n-1:
return 0
if memo[p] != n:
return memp[p]
steps = nums[p]
for i in range(1,steps+1):
subProblem = dp(nums,p+i)
memo[p] = min(memo[p],subProblem+1)
return memo[p]
n = len(nums)
memo = [n for i in range(n)]
return dp(nums,0)
贪心算法:
def jump(nums):
n = len(nums)
end = 0
count = 0
farthest = 0
for i in range(n-1):
farthest = max(nums[i] + i,farthest)
if end == i:
count += 1
end = farthest
return count
数学图像:
def canCompleteCircuit(gas,cost):
n = len(gas)
sum = 0
minSum = 0
start = 0
for i in range(n):
sum += gas[i] - cost[i]
if sum < minSum:
start = i + 1
minSum = sum
if sum < 0:
return -1
return start if start != n else 0
贪心算法:
def canCompleteCircuit(gas,cost):
n = len(gas)
sum = 0
for i in range(n):
sum += gas[i] - cost[i]
if sum < 0:
return -1
tank = 0
start = 0
for i in range(n):
tank += gas[i] - cost[i]
if tank < 0:
tank = 0
start = i+1
return start if start != n else 0