1、 dp 1.1 0-1背包 01背包问题: 1、dp[i][j]定义:前i个物品,背包的最大价值或重量 2、递推公式: (a) j>=v[i]时,dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]) (b) j<v[i]时,dp[i][j] = dp[i-1][j] 3、边界条件:i=0或j=0时,dp[i][j] = 0 特别注意:由于dp多了一行和一列,读v[i]、w[i]时,用下标i-1 class Solution: def knapsack(self , V , n , vw ): # write code here #print('V=',V) #print('n=',n) #print('vw=',vw) dp = [[0]*(V+1) for _ in range(n+1)] for i in range(n+1): v_i = vw[i-1][0] w_i = vw[i-1][1] for j in range(V+1): if i>0 and j>0: if j >= v_i: dp[i][j] = max(dp[i-1][j], dp[i-1][j-v_i] + w_i) else: dp[i][j] = dp[i-1][j] # print(dp[-1][-1]) return dp[-1][-1] 1.2 带条件的0-1背包 https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4?tpId=37&&tqId=21239&rp=1&ru=/ta/huawei&qru=/ta/huawei/question-ranking w_max,n = map(int,input().strip().split()) w_max = w_max//10 #print('w_max=',w_max,'n=',n) T = [] for i in range(n): v,w,q = map(int,input().strip().split()) T.append([v//10,w*v//10,q]) #print('T=',T) #========================================== # 处理主件 W = [[] for _ in range(n)] V = [[] for _ in range(n)] I = [] for i in range(n): if T[i][2]==0: W[i].append(T[i][0]) V[i].append(T[i][1]) I.append(i) #print('W=',W) #print('V=',V) #print('I=',I) #========================================== # 处理附件 for i in range(n): if T[i][2]>0: ii = T[i][2]-1 W[ii].append(T[i][0]) V[ii].append(T[i][1]) #print('W=',W) #print('V=',V) #========================================== # 附件的排列组合 for i in range(n): if len(W[i])==2: W[i] = [W[i][0], W[i][0]+W[i][1]] V[i] = [V[i][0], V[i][0]+V[i][1]] if len(W[i])==3: W[i] = [W[i][0], W[i][0]+W[i][1], W[i][0]+W[i][2], W[i][0]+W[i][1]+W[i][2]] V[i] = [V[i][0], V[i][0]+V[i][1], V[i][0]+V[i][2], V[i][0]+V[i][1]+V[i][2]] #print('W=',W) #print('V=',V) #========================================== DP = [[0]*(w_max+1) for _ in range(len(I)+1)] for i in range(1,len(I)+1): ii = I[i-1] for j in range(1,w_max+1): t_max = DP[i-1][j] for k in range(len(W[ii])): W_i = W[ii][k] V_i = V[ii][k] if j>=W_i: t_max = max(t_max, DP[i-1][j-W_i]+V_i) DP[i][j] = t_max #print('DP=',DP) print(DP[-1][-1]*10) 1.3 最长增长子序列 dp = [1]*n for i in range(1,n): # 第i个桩高度L[i] for j in range(0,i): # 第0~i-1个桩调度L[0]~L[i-1] if L[i]>L[j]: # 如果存在L[i]>L[j]则maxLen[i]需更新 dp[i] = max(dp[j]+1, dp[i]) # 在0<=j<=i-1中,保留更大的更新 print(dp) 1.4 最长回文子串 dp[i][j]定义为:如果dp[i][j]为1,则表示字符串从i到j是回文子串, 如果dp[i][j]为0,则表示字符串从i到j不是回文子串。 1、当i==j时,dp[i][j]==1 2、当j-i<=2时,如s[i]==s[j],dp[i][j]==1 3、其它情况,如s[i]==s[j],当dp[i+1][j-1]==1,则dp[i][j]==1 由于递推公式: dp[i][j] = dp[i+1][j-1]==1,条件是s[i]==s[j],i要从大的数值,j要从小的数值开始计算 dp = [[0]*n for _ in range(n)] maxlen = 1 str1 = '' for i in range(n-1,-1,-1): for j in range(i,n): if A[i]==A[j]: if j-i<=2: dp[i][j] = 1 else: dp[i][j] = dp[i+1][j-1] if dp[i][j]==1 and j-i+1>maxlen: maxlen = j-i+1 str1 = A[i:j+1] 1.5 最长回文子序列 dp[i][j]定义为:字符串从i到j是含有回文子序列的长度 1、当i==j时,dp[i][j]==1 2、当j-i<=2时,如s[i]==s[j],dp[i][j] = j-i+1 3、当j-i>2时, 如s[i]==s[j],dp[i][j] = dp[i+1][j-1]+2 4、如s[i]!=s[j]时,dp[i][j] = max(dp[i][j-1], dp[i+1][j]) 由于递推公式: dp[i][j] = dp[i+1][j-1]+2,i要从大的数值,j要从小的数值开始计算 class Solution: def longestPalindromeSubSeq(self, A, n): # write code here #print(A) #print(n) dp = [[0]*n for _ in range(n)] for i in range(n-1,-1,-1): for j in range(i,n): if A[i]==A[j]: if j-i<=2: dp[i][j] = j-i+1 else: dp[i][j] = dp[i+1][j-1]+2 else: dp[i][j] = max(dp[i][j-1], dp[i+1][j]) #for i in dp: # print(i) return dp[0][-1] print(max(dp)) # 对所有maxLen[i]取最大 1.6 最长公共子串 dp[i][j]定义为:str1在索引i处,str2在索引j处,存在公共子串的最大长度(即至少存在str1[i]==str2[j]) 需要记录所有的ij处存在公共子串的最大长度,及对应的i或j(子串终点) 1、当i==0 | j==0: (a)当str1[i] == str2[j],dp[i][j] = 1 2、其它情况: (b)当str1[i] == str2[j],dp[i][j] = dp[i-1][j-1] + 1 class Solution: def LCS(self , str1 , str2 ): # write code here r = len(str1) c = len(str2) dp = [[0]*c for _ in range(r)] maxlen = 0 endstr1 = 0 for i in range(r): for j in range(c): if (i==0 or j==0): if str1[i]==str2[j]: dp[i][j] = 1 else: if str1[i]==str2[j]: dp[i][j] = dp[i-1][j-1]+1 if dp[i][j]>maxlen: maxlen = dp[i][j] endstr1 = i if maxlen>0: return str1[endstr1-maxlen+1:endstr1+1] 1.7 最长公共子序列 dp[i][j]定义为:str1在索引i处及之前位置,str2在索引j处及之前位置,存在公共子序列的最大长度 需要回溯取依据取公共子序列字符 1、当i==0 | j==0: (a)当str1[i] == str2[j],dp[i][j] = 1 (b)当str1[i] != str2[j],dp[i][j] = max(dp[i-1][j], dp[i][j-1]) 2、其它情况: (c)当str1[i] == str2[j],dp[i][j] = dp[i-1][j-1] + 1 (d)当str1[i] != str2[j],dp[i][j] = max(dp[i-1][j], dp[i][j-1]) class Solution: def LCS(self , str1 , str2 ): # write code here if str1 == '' or str2 == '': return '-1' r = len(str1) c = len(str2) dp = [[0]*(c+1) for _ in range(r+1)] for i in range(1,1+r): for j in range(1,1+c): if (i==1 or j==1): #print('A--',i,j) if str1[i-1]==str2[j-1]: dp[i][j] = 1 else: dp[i][j] = max(dp[i][j-1],dp[i-1][j]) else: #print('B--',i,j) if str1[i-1]==str2[j-1]: dp[i][j] = dp[i-1][j-1]+1 else: dp[i][j] = max(dp[i][j-1],dp[i-1][j]) i = r j = c substr = '' while i>=1 and j>=1: if str1[i-1]==str2[j-1]: substr += str1[i-1] i -= 1 j -= 1 else: if dp[i-1][j]>dp[i][j-1]: i -= 1 else: j -= 1 #print(substr[::-1]) if substr != '': return substr[::-1] else: return '-1' 1.8 最小编辑代价 动态规划 1.dp[i][j]定义 字符串str1[0~i]编辑成字符串str2[0~j]的需要的最小编辑代价 2.状态转移方程 当str2[i] == str1[j],dp[i][j] = dp[i-1][j-1] 当str2[i] != str1[j],dp[i][j] = min(dp[i][j-1]+ic, dp[i-1][j]+dc, dp[i-1][j-1]+rc) # 插入、删除、替换比小 3.边界 dp[0][0] = 0 dp[0][j] = j*ic,j次插入 dp[i][0] = i*dc,i次删除 r = len(str1) c = len(str2) dp = [[0]*(c+1) for _ in range(r+1)] for i in range(r+1): for j in range(c+1): if i==0 and j==0: dp[i][j]=0 elif i==0: dp[0][j] = j*ic # j变化插入操作 elif j==0: dp[i][0] = i*dc # i变量删除操作 else: if str1[i-1]==str2[j-1]: dp[i][j] = dp[i-1][j-1] # 对应字符相等则代价不变,如不等则需替换操作 else: dp[i][j] = min(dp[i][j-1]+ic, dp[i-1][j]+dc, dp[i-1][j-1]+rc) # 插入、删除、替换比小 for i in dp: print(i) return dp[-1][-1] 1.9 子数组的最大累加和 t = 0 maxsum1 = 0 for i in arr: t += i if t<0: t=0 elif t>maxsum1: maxsum1 = t 1.10 子数组最大乘积 1、要同时记住每次乘积之后的最大,最小(比较时要含自己) 2、最大最小要存个临时变量以防覆盖 class Solution: def maxProduct(self , arr ): # write code here #print(arr) n = len(arr) t_max = arr[0] t_min = arr[0] maxprod = arr[0] for i in range(1,n): t_max2 = max(t_max*arr[i], arr[i], t_min*arr[i]) t_min2 = min(t_max*arr[i], arr[i], t_min*arr[i]) t_max = t_max2 t_min = t_min2 if t_max > maxprod: maxprod = t_max return maxprod 1.11 矩阵的最小路径和 dp[i][j]定义为:从左上角到当前位置的最短路径,m[i][j]当前位置上的路径值, 核心迭代公式为, dp[i][j] = min(dp[i][j-1],dp[i-1][j]) + m[i][j] 即,到当前位置的最短路径 = 从上和从左方向来的路径的最小值 + 当前位置上的路径值 dp = [[0]*c for _ in range(r)] dp[0][0] = m[0][0] for i in range(r): for j in range(c): if (i==0 and j>0): dp[i][j] = dp[i][j-1] + m[i][j] elif (j==0 and i>0): dp[i][j] = dp[i-1][j] + m[i][j] else: dp[i][j] = min(dp[i][j-1],dp[i-1][j]) + m[i][j] 1.12 不同路径数 dp[i][j]表示前i行、j列的路径数,状态公式如下: 如果i >= 2 && j >= 2,那么dp[i][j] = dp[i-1][j] + dp[i][j-1] dp[1][k] = 1 dp[k][1] = 1 解释如下: 当列数和行数大于2的时候,当前节点的路径数为左侧节点路径数+右侧节点路径数 当列数或行数为1时,当前节点的路径数恒为1 class Solution: def uniquePaths(self , m , n ): # write code here dp = [[0]*n for _ in range(m)] for i in range(m): for j in range(n): if (i==0 or j==0): dp[i][j] = 1 else: dp[i][j] = dp[i-1][j]+dp[i][j-1] return dp[-1][-1] 1.13 最大正方形面积 1.确定dp[][]数组的含义 此题的dp[i][j],代表以坐标为(i,j)的元素为右下角的正方形的边长。 2.状态转移方程 dp[i][j]的值取决于dp[i-1][j],dp[i-1][j-1],dp[i][j-1]的最小值 即左方正方形的边长,左上方正方形的边长,上方正方形的边长三者的最小值。 3.边界 由于状态转移方程中涉及i-1,j-1,所以i和j一定要大于0. 故dp[0][] 和 dp[][0]要首先确定。 class Solution: def solve(self , matrix ): m = matrix r = len(m) c = len(m[0]) dp = [[0]*c for _ in range(r)] maxLen = 0 for i in range(r): for j in range(c): if (i==0 or j==0) and m[i][j]==1: dp[i][j] = 1 elif m[i][j]==1: dp[i][j] = min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])+1 if dp[i][j]>maxLen: maxLen = dp[i][j] #print('maxLen=',maxLen) return maxLen*maxLen 1.14 把数字翻译成字符串 1.dp[i]定义 字符串 nums[0-i] 的可能的译码结果 2.状态转移方程 (i>=2) (a)后两个字符11-19,21-26时,dp[i] = dp[i-1] + dp[i-2] (b)当前字符1-9时,后两个字符10或20时,dp[i] = dp[i-1] (c)其它情况 dp[i] = 0 3.边界 (i==0) 首字符1-9时,dp[0] = 1 首字符为0时,dp[0] = 0,直接返回 (i==1) (a)后两个字符11-19,21-26时,dp[i] = 2 (b)当前字符1-9时,后两个字符10或20时,dp[i] = 1 (c)其它情况 dp[i] = 0 dp = [0]*n for i in range(n): if i==0: if 1<=int(nums[i])<=9: dp[i] = 1 else: dp[i] = 0 return 0 elif i==1: if 11<=int(nums[i-1:i+1])<=19 or 21<=int(nums[i-1:i+1])<=26: dp[i] = 2 elif 1<=int(nums[i])<=9 or int(nums[i-1:i+1])==10 or int(nums[i-1:i+1])==20: dp[i] = 1 else: dp[i] = 0 else: if 11<=int(nums[i-1:i+1])<=19 or 21<=int(nums[i-1:i+1])<=26: dp[i] = dp[i-1] + dp[i-2] elif 1<=int(nums[i])<=9 or int(nums[i-1:i+1])==10 or int(nums[i-1:i+1])==20: dp[i] = dp[i-1] else: dp[i] = 0