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