赞
踩
一次只能上一阶,或者两阶。n个台阶几种走法。
(1)递归
class Solution:
def climbStairs(self,n):
if n==1:
return 1
if n==2:
return 2
return self.climbStairs(n-1)+self.climbStairs(n-2)
(2)dp
class Solution:
def climbStairs(self, n):
if n == 1:
return 1
if n == 2:
return 2
dp = [0 for _ in range(n + 1)]
dp[1] = 1
dp[2] = 2
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[-1]
题目描述:抢劫一排住户,但是不能抢邻近的住户,求最大抢劫量。[1,2,3,1] ,偷窃到的最高金额 = 1 + 3 = 4 。
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) == 0:return 0
if len(nums) == 1:return nums[0]
if len(nums) == 2:return max(nums)
res = [0]*len(nums)
res[0]=nums[0]
res[1]=max(nums[1], nums[0])
for i in range(2,len(nums)):
res[i] = max(res[i-1],res[i-2]+nums[i])
return res[-1]
上一题是一个单list。本题目是list的首尾要重合。
class Solution: def rob_(self, nums: List[int]) -> int: res = [0]*len(nums) res[0]=nums[0] res[1]=max(nums[1], nums[0]) for i in range(2,len(nums)): res[i] = max(res[i-1],res[i-2]+nums[i]) return res[-1] def rob(self, nums: List[int]) -> int: if len(nums) == 0:return 0 if len(nums) == 1:return nums[0] if len(nums) == 2:return max(nums) res1 = self.rob_(nums[0:-1]) res2 = self.rob_(nums[1::]) return max(res1,res2)
通向公式需要每次都从最优里面再次遍历一遍。
本题目是这个系列的基础
(1)dp存放的是当前位置的最大值,是里层循环每一个位置+1的值遍历取max。
class Solution:
def lengthOfLIS(int[] nums):
dp=[1]*len(nums)
for i in range(1,len(nums)):
for j in range(i):
if nums[i]>nums[j]:
#最后一个位置比当前大,说明当前应该有多1个的子序列
dp[i]=max(dp[i],dp[j]+1)
return max(dp)
与300题一样
class Solution:
def findLongestChain(nums):
nums = sorted(nums, key=lambda x: x[1])
dp = [1]*len(nums)
for i in range(1,len(nums)):
for j in range(i):
if nums[j][1]<nums[i][0]:
dp[i]=max(dp[i],dp[j]+1)
return max(dp)
解法一:
本题和回溯问题中海水流向题都需要问题解耦,一个基本子模块只完成一个任务,有多个任务时候需要上多个子模块。
(1). 常规想法,用两个dp数组,遍历序列上扬和下降,与300题目不同的是,如果当前是上扬的,需要在所有下降里找到最大,如果当前是下降的则在所有上扬数组找最大。
(2).既然是找当前的最大,那就是dp,实现正常dp的思路即可。
class Solution(object):
def wiggleMaxLength(self, nums):
inc, dec = [1] * len(nums), [1] * len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[i] > nums[j]:
inc[i] = max(inc[i],dec[j]+1)
elif nums[i] < nums[j]:
dec[i] = max(dec[i],inc[j]+1)
return max(dec[-1],inc[-1])
class Solution(object): def wiggleMaxLength(self, nums): """ [0, 1,2, 3, 4, 5, 6,7, 8,9] 输入: [1,17,5,10,13,15,10,5,16,8] inc [1, 2,2, 4,4,4,4,4, 6,6] dec [1, 1,3, 3,3,3,5,5,5,7] [1, 2,3, 4, 4, 4,5,5, 6,7] """ n = len(nums) if n <= 1: return n inc, dec = [1] * n, [1] * n for x in range(1, n): if nums[x] > nums[x - 1]: inc[x] = dec[x - 1] + 1 dec[x] = dec[x - 1] elif nums[x] < nums[x - 1]: dec[x] = inc[x - 1] + 1 inc[x] = inc[x - 1] else: inc[x] = inc[x - 1] dec[x] = dec[x - 1] return max(inc[-1], dec[-1])
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
class Solution: def maxProduct(self, nums: List[int]) -> int: max_dp = copy.deepcopy(nums) min_dp = copy.deepcopy(nums) for i in range(1,len(nums)): max_dp[i] = max(max_dp[i],nums[i]*max_dp[i-1],nums[i]*min_dp[i-1]) min_dp[i] = min(min_dp[i],nums[i]*max_dp[i-1],nums[i]*min_dp[i-1]) return max(max(max_dp),max(min_dp)) # [2,6,-2,4] # [2,3,-12,-48] max_dp = [float('-inf')] *len(nums) min_dp = [float('inf')] *len(nums) max_dp[0] = nums[0] min_dp[0] = nums[0] for i in range(1,len(nums)): max_dp[i] = max(nums[i],nums[i]*max_dp[i-1],nums[i]*min_dp[i-1]) min_dp[i] = min(nums[i],nums[i]*max_dp[i-1],nums[i]*min_dp[i-1]) return max(max(max_dp),max(min_dp))
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
dp = [0]*len(nums)
dp[0] =nums[0]
for i in range(1,len(nums)):
if dp[i-1]>0:
dp[i] = nums[i]+dp[i-1]
else:
dp[i] = nums[i]
return max(dp)

class Solution:
def maxEnvelopes(self, envelopes: List[List[int]]) -> int:
envelopes.sort(key = lambda x:(x[0],-x[1]))
length = len(envelopes)
dp =[1]*length
for i in range(length):
for j in range(i):
if envelopes[i][1]>envelopes[j][1]:
dp[i]=max(dp[i],dp[j]+1)
return max(dp)
题目描述:For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).
(1).最大还是想到dp,其次,既然是编程实现,则要考虑如何实现遍历和搜索,所有数字分割的都是逐个拆,与 两数求和题目 一样如下的遍历。
(2).利用300题目的经典搜索思路,很容易思考到mem[i] = max(mem[i],j*mem[i-j]),但是结果不对,大方向又没问题,思考后原因是因为j * mem[i - j],这个至少是三项相乘,因为mem[i-j]至少是两项相乘,所以要补充一个两项相乘j * (i-j)。
class Solution:
def integerBreak(self, n):
if n == 1:
return 1
mem = [-1 for i in range(n + 1)]
mem[1] = 1
for i in range(2, n + 1):
for j in range(1, i):
#与递归不同是从前往后走,把前面的都记录下来
mem[i] = max(mem[i], j*(i - j), j*mem[i - j])
return mem[-1]
递归写法
class Solution:
def integerBreak(self, n):
if n == 1:
return 1
result = -1
for i in range(1, n):
#注意这里和dp的不同,这里是固定了n,找所有i的可能性的最大值
result = max(result, i*(n - i), i * self.integerBreak(n - i))
return result
补充一个思路,乘积最大应该是拆分的几个数差异最相近,初中的几个平均数的概念,利用这个编程也是AC的,但是为了思路的一致性,不写上来了。
补充两数求和:
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
class Solution(object):
def twoSum(self, nums, target):
for i in range(len(nums)):
num = target - nums[i]
if num in nums[i+1:]:
return [i, nums.index(num,i+1)]
(1).没有思路的时候想计算机要利用搜索的方式完成任务。
题目描述:For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。与343不同的是,本题目只需要找到所有最小的分割,不存在343的乘积会有遗漏的问题。
class Solution(object):
def numSquares(self, n):
output = [float('inf')] * (n + 1)
output[0] = 0
output[1] = 1
for i in range(2, n + 1):
j = 1
while (j * j <= i):
#与爬楼梯类似,发现了一个合理点i-j**2,则应该在此+1,爬楼梯是dp[n]=dp[n-1]+dp[n-2],表示用一步上还是两步上
output[i] = min(output[i], output[i - j * j] + 1)
j = j + 1
return output[-1]
题目描述:Given encoded message “12”, it could be decoded as “AB” (1 2) or> “L” (12)
通向只依赖前两个,不需要从头再次遍历。
class Solution: def numDecodings(self, s): if not s: return 0 return self._numDecodings(s) def _numDecodings(self, nums): #一路拆解,如果为空了,那就说明找到了1种解法 if not nums: return 1 #返回一个求和的数,所以定义在这 result = 0 #也可以是num[0]!=0,不等于0即1~9再无其他 if 1 <= int(nums[0]) <= 9: result += self._numDecodings(nums[1:]) if len(nums) >= 2 and 10 <= int(nums[0:2]) <= 26: result += self._numDecodings(nums[2:]) return result
注意:
(1)程序进入时候如果是空串返回0种解码;
(2)在递归函数里,使用两个并列的if对一个result求和,这个逻辑非常重要,蕴含了上述伪代码的所有情况,两个是if是并列依次执行的,不满足则跳过,这样的双if在树bfs建树和bfs遍历树也是这样使用。所以逻辑不好写的时候想想双if,而且逻辑的解耦,比如本题目只做了加法应该的情况。
(3)在递归里面如果nums为空了,说明分解完了,即找到了,return应该是1。一个case是10,进入了第二个if,分解之后nums是空,其实是找到了,应该return1,但是对于输入的纯空串又矛盾,所以建立(1)。
(4)不一定是所有都是if xxx:return。比如这个直接设置初值,进入不了if的就return初始值。
class Solution: def numDecodings(self, s): # dp[i] = dp[i-1](if s[i] is vaild)+dp[i-2](id s[i-2:i] is vaild) # dp[1]-->dp[-1] #s 1 2 3 4 5 0 #dp 1 1 if not s or s[0] == '0': return 0 dp = [0]*(len(s)+1) #dp[0] = 1 if s[0]!='0' else 0 #dp[1] = 1 if s[0]!='0' else 0 dp[0]=dp[1]=1 for i in range(2,len(s)+1): if s[i-1] != '0': dp[i] = dp[i-1] if '10'<=s[i-2:i]<='26': dp[i] += dp[i-2] return dp[-1]
(1)递归的思路是f(s[:])=f(s[1:])+f(s[2:]),dp的思路是f(i)=f(i-1)(where s[i] valid)+f(i-2)(where s[i-1:i+1] valid) i>=2 ,正好是反着来,整个条件和写法都是倒着来,但是为了凑出f(1)=f(0)+f(-1)的这个-1需要多往后凑一个,初学的时候总是会正反觉得都可以,确实如此,和顺序无关,因此dp和递归才能转化。不过这个dp不需要如300题形成图,每次都从头扫描过来,只需要扫描前两个,看通项公式。所以只需要手动把最初始的case解决就可以了。
(2)上面的描述体现了dp的两个重点,通项公式,初始化。
[[1,3,1],
[1,5,1],
[4,2,1]]
只能向下或者向右走。
Given the above grid map, return 7. Because the path 1→3→1→1→1 minimizes the sum.
(1).递归思路很自然,接近人的思路
先给一个比较通用的方法,以后类似的代码都是这样的解,用一个变量保存要返回的值,而不是下面的递归都在return眼花缭乱。
import numpy as np class Solution(): def minPathSum(self, arr): arr = np.array(arr) return self._minimum_path_sum(arr, 0, 0) def _minimum_path_sum(self, arr, i, j): # 与双if类似,需要考虑if的顺序,本题需要优先考虑是结尾就立马停下来 if i == arr.shape[0] - 1 and j == arr.shape[1] - 1: return int(arr[i][j]) elif i == arr.shape[0] - 1: res = arr[i][j] + self._minimum_path_sum(arr, i, j + 1) elif j == arr.shape[1] - 1: res = arr[i][j] + self._minimum_path_sum(arr, i + 1, j) else: right = self._minimum_path_sum(arr, i, j + 1) down = self._minimum_path_sum(arr, i + 1, j) res = arr[i][j] + min(right, down) return int(res)
import numpy as np class Solution(): def minPathSum(self, arr): arr = np.array(arr) return self._minimum_path_sum(arr, 0, 0) def _minimum_path_sum(self, arr, i, j): # 与双if类似,需要考虑if的顺序,本题需要优先考虑是结尾就立马停下来 res = 0 if i == arr.shape[0] - 1 and j == arr.shape[1] - 1: return int(arr[i][j]) elif i == arr.shape[0] - 1: tmp = arr[i][j] + self._minimum_path_sum(arr, i, j + 1) res += tmp elif j == arr.shape[1] - 1: tmp = arr[i][j] + self._minimum_path_sum(arr, i + 1, j) res += tmp else: right = self._minimum_path_sum(arr, i, j + 1) down = self._minimum_path_sum(arr, i + 1, j) tmp = arr[i][j] + min(right, down) res += tmp return int(res)
个人不是很喜欢的方式递归:
class Solution(): def minimum_path_sum(self,arr): arr = np.array(arr) return self._minimum_path_sum(arr, 0, 0) def _minimum_path_sum(self,arr,i,j): #与双if类似,需要考虑if的顺序,本题需要优先考虑是结尾就立马停下来 if i==arr.shape[0]-1 and j==arr.shape[1]-1: return int(arr[i][j]) elif i==arr.shape[0]-1: return arr[i][j]+self._minimum_path_sum(arr,i,j+1) elif j==arr.shape[1]-1: return arr[i][j]+self._minimum_path_sum(arr,i+1,j) right = self._minimum_path_sum(arr,i,j+1) down = self._minimum_path_sum(arr,i+1,j) return int(arr[i][j]+min(right,down))
(2).dp解法:
import numpy as np class Solution: def dp_minimum_path_sum(A): A = np.array(A) dp=np.zeros_like(A) dp[0][0]=A[0][0] #如果在最上行和最左边只有一种走法 for i in range(A.shape[0]): dp[i][0] = A[i][0]+dp[i-1][0] for j in range(A.shape[1]): dp[0][j] = A[0][j] + dp[0][j-1] for i in range(1,A.shape[0]): for j in range(1,A.shape[1]): dp[i][j] = A[i][j] + min(dp[i-1][j],dp[i][j-1]) return dp[-1][-1]
题目描述:统计从矩阵左上角到右下角的路径总数,每次只能向右或者向下移动。
(1).递归
def count_path_sum(A,x,y):
if A.shape[0] == 1 and A.shape[1]==1:
return 1
if x==A.shape[0]-1:
return 1
if y==A.shape[1]-1:
return 1
return count_path_sum(A,x+1,y)+count_path_sum(A,x,y+1)
(2).dp
import numpy as np
class Solution:
def dp_count_path_sum(A):
dp = np.zeros_like(A)
dp[:,0]=1
dp[0,:]=1
for i in range(1,A.shape[0]):
for j in range(1,A.shape[1]):
dp[i][j]=dp[i-1][j]+dp[i][j-1]
return int(dp[-1][-1])
Given nums = [-2, 0, 3, -5, 2, -1]
sumRange(0, 2) -> 1 sumRange(2, 5) -> -1 sumRange(0, 5) -> -3
class NumArray:
def __init__(self, nums: List[int]):
self.num_sum = [0,]
for i in range(len(nums)):
self.num_sum.append(self.num_sum[i]+nums[i])
def sumRange(self, i: int, j: int) -> int:
return self.num_sum[j+1] - self.num_sum[i]
A = [0, 1, 2, 3, 4]
return: 6, for 3 arithmetic slices in A:
[0, 1, 2], [1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3, 4], [ 1, 2, 3, 4], [2, 3, 4]
思路很容易想到能不能用到前面的结论,如果前面是等差数列,下一个数也满足条件则dp+1 。
直接找等差序列是连续的,所以不必每次从头再遍历一遍,只需要就近的通向。如果只有[1,2,3,4]1,2,3是等差,3的位置dp=1,4的位置dp[i]=dp[i-1]+1 = 2,意思是有1,2,3,4和2,3,4,最终结果为1+2 = 3
class Solution:
def numberOfArithmeticSlices(self, A):
n = len(A)
if n < 3:
return 0
dp = [0] * n
for i in range(2, n):
if A[i] - A[i - 1] == A[i - 1] - A[i - 2]:
dp[i] = dp[i - 1] + 1
return sum(dp)

(1)递归方法
class Solution: def longestCommonSubsequence(text1,text2): if text1 == '' or text2 == '': return 0 if text1[-1]==text2[-1]: return 1+longestCommonSubsequence(text1[:-1],text2[:-1]) else: a_less = longestCommonSubsequence(text1[:-1], text2) b_less = longestCommonSubsequence(text1, text2[:-1]) return max(a_less,b_less) #第二种传递参数的方法 def CLS2(str1,str2): def CLS2_(i,j): maxval=0 if i == -1 or j == -1: return 0 if str1[i] == str2[j]: return CLS2_(i-1,j-1)+1 else: return max(CLS2_(i-1,j),CLS2_(i,j-1)) return CLS2_(len(str1) - 1, len(str2) - 1)
(2)dp方法
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
l = [[0]*(n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i-1] == text2[j-1]:
l[i][j] = l[i-1][j-1] + 1
else:
l[i][j] = max(l[i-1][j], l[i][j-1])
return l[-1][-1]
(1).循环实现
class Solution:
def CLS2(str1, str2):
maxlen = 0
str1len = len(str1)
str2len = len(str2)
for i in range(str1len):
for j in range(str2len):
len_ = 0
while i + len_ < str1len and j + len_ < str2len and \
str1[i + len_] == str2[j + len_]:
len_ += 1
maxlen = max(len_, maxlen)
return maxlen
(2)dp实现
def LCstring(string1,string2):
len1 = len(string1)
len2 = len(string2)
res = [[0 for i in range(len1+1)] for j in range(len2+1)]
result = 0
for i in range(1,len2+1):
for j in range(1,len1+1):
if string2[i-1] == string1[j-1]:
res[i][j] = res[i-1][j-1]+1
result = max(result,res[i][j])
return result
print(LCstring("helloworld","loop"))
(3)递归实现(不成功,但是值得分析)
def CLS2(str1,str2):
def CLS2_(i,j):
maxval=0
if i == -1 or j == -1:
return 0
if str1[i] == str2[j]:
val = CLS2_(i-1,j-1)+1
maxval = max(maxval,val)
else:
return 0
CLS2_(i - 1, j)
CLS2_(i, j - 1)
return maxval
军神提出来的版本,是不是网上首个递归方法的求解
class Solution: def CLS2(self,str1,str2): self.maxval = 0 visited = {} def CLS2_(i,j): val = 0 if (i, j) not in visited: if i == -1 or j == -1: return 0 #军神提出了这点,包含了所有所有情况,如果该位相等,要看前一位是不是也相等,对这个问题进一步的分类讨论 if str1[i] == str2[j]: if str1[i-1] == str2[j-1]: val = CLS2_(i-1,j-1)+1 else: #这里不是直接return,而是复制val,因为本方法返回的是val val = 1 self.maxval = max(self.maxval, val) CLS2_(i - 1, j) CLS2_(i, j - 1) visited[(i,j)]=val return val else: return visited[(i,j)] CLS2_(len(str1) - 1, len(str2) - 1) return self.maxval
上面的子序列求解两个字符串关系,以及下面的最大正方形和下面的背包问题都是两个维度的dp。
一个旅行者有一个最多能装M公斤的背包,现在有n件物品,他们的重量分别是W1,W2…Wn ,他们的价值分别是C1,C2,…Cn。求旅行者得到的最大价值。
分析:一个背包,容量有限,要求装的东西价值最大,而且装法很多,拿与不拿很混乱,很显然可以用dp解决。 涉及到的约束很多:背包容量,物品重量,物品价值,背包的最大价值。建立一个能融合在一起的dp,这种混乱的问题都思考是否有后向无效原则即:当前的取值只与前一次取值有关,不必要再往前思考。
dp做一个二维表,行表示当前所拿到的物品,列为当前的容量。
第一行和第一列都为0表示0个物品价值为0,0个空间没法拿东西,价值为0。
由当前包空间是否放得下当前物品,可以有如下表和转移公式:


class Soultion: def __init__(self,n,v,c): self.things_weight= n#list self.values = v#list self.capacity = c#num def knapsack01(self): dp=[[0 for _ in range(self.capacity)] for _ in range(len(self.things_weight))] #外层循环是物品 for i in range(len(self.things_weight)): #内层循环是背包容量 for j in range(self.capacity): if self.things_weight[i] > j: dp[i][j] = dp[i-1][j] else: #分成拿与不拿,看哪个情况下的值大 dp[i][j] = max(dp[i][j-self.num_things[i]]+self.values[i],dp[i][j-1]) return dp[-1][-1]
(2)优化存储空间,因为后无效性。只需要一维向量存储就可以。如下图,去了i仅仅保留j。滚动数组。

class Soultion:
def __init__(self,n,v,c):
self.things_weight = n#list
self.values = v#list
self.capacity = c#num
def knapsack01(self):
dp = [0 for _ in range(self.capacity)]
for i in range(len(self.things_weight)):
#这是重点!!!必须从后往前循环,没有if直接判断谁大谁小直接上结果
for j in range(self.capacity,0,-1):
dp[j] = max(dp[j],dp[j-self.things_weight[i]]+self.values[i])
class Soultion: def __init__(self,n,v,c): self.things_weight= n#list self.values = v#list self.capacity = c#num def knapsack01(self): dp=[[0 for _ in range(self.capacity)] for _ in range(len(self.things_weight))] #外层循环是物品 for i in range(len(self.things_weight)): #内层循环是背包容量 for j in range(self.capacity): if self.things_weight[i] > j: dp[i][j] = dp[i-1][j] else: for k in range(j//self.things_weight[i]): #分成拿与不拿,看哪个情况下的值大 dp[i][j] = max(dp[i][j-k*self.num_things[i]]+k*self.values[i],dp[i][j-1]) return dp[-1][-1]
在01背包优化的基础上再次优化一下。
class Soultion:
def __init__(self,n,v,c):
self.things_weight = n#list
self.values = v#list
self.capacity = c#num
def knapsack01(self):
dp = [0 for _ in range(self.capacity)]
for i in range(len(self.things_weight)):
#这是重点!!!必须从后往前循环
for j in range(self.capacity,0,-1):
if j>self.things_weight[i]:
for k in range(j//self.things_weight[i]):
dp[j] = max(dp[j],dp[j-k*self.things_weight[i]]+k*self.values[i])
再次优化:

class Soultion:
def __init__(self,n,v,c):
self.things_weight = n#list
self.values = v#list
self.capacity = c#num
def knapsack01(self):
dp = [0 for _ in range(self.capacity)]
for i in range(len(self.things_weight)):
#这是重点!!!必须从前往后遍历,时刻改变本行的值
for j in range(self.things_weight[i],self.capacity):
dp[j] = max(dp[j],dp[j-self.things_weight[i]]+self.values[i])
有N种物品和容量为V的背包,第i件物品最多有n[i]个,每个物品重量和价值一直,求将哪些放进来总价值最大。
问题分析:
01背包只能放1个,完全背包能放无限个,多充背包放有限个。
先从01背包开始优化。每个物品有s[i]个
class Soultion: def __init__(self,n,v,c,s): self.things_weight = n#list self.values = v#list self.capacity = c#num self.single_counts = s def knapsack01(self): dp = [0 for _ in range(self.capacity)] for i in range(len(self.things_weight)): #这是重点!!!必须从后往前循环 for j in range(self.capacity,0,-1): k =0 #与完全背包的差异 while (k>=self.single_counts[i] and k*self.things_weight[i]<=j): k+=1 dp[j] = max(dp[j],dp[j-k*self.things_weight[i]]+k*self.values[i])
Input: [1, 5, 11, 5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].
(1)回溯法
class Solution: def canPartition_(self, nums): """ :type nums: List[int] :rtype: bool """ nums_sum=sum(nums) if nums_sum%2!=0: return False else: nums_sum//=2 rec=[False] def search(nums,i,s=0): if i==len(nums) or s>nums_sum: return if nums[i]+s==nums_sum: rec[0]=True return else: search(nums,i+1,s) search(nums,i+1,s+nums[i]) search(nums,0,0) return rec[0]
(2).dp背包
class Solution:
def canPartition(nums):
nums_sum=sum(nums)
if nums_sum%2!=0:
return False
else:
nums_sum//=2
dp = [False]*(nums_sum+1)
dp[0] = True
#背包问题之所以和物品顺序无关要先遍历物品再遍历空间
for i in nums:
for j in range(nums_sum,i+1,-1):
dp[j] = dp[j] or dp[j-i]
return dp[nums_sum+1]
一个数组改变符号,和为目标结果。
Input: nums is [1, 1, 1, 1, 1], S is 3. Output: 5
Explanation:-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3There are 5 ways to assign symbols to make the sum of nums be target 3.
class Solution:
def findTargetSumWays(nums, S):
def helper(index, acc):
if index == len(nums):
#注意,必须是走到底才能判断说是对还是不对
if acc == S:
return 1
else:
return 0
return helper(index + 1, acc + nums[index]) + helper(index + 1, acc - nums[index])
return helper(0, 0)
在这里插入代码片
Input: Array = {“10”, “0001”, “111001”, “1”, “0”}, m = 5, n = 3 Output: 4
Explanation: There are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are “10”,“0001”,“1”,“0”
在这里插入代码片
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
Input: Array = {“10”, “0001”, “111001”, “1”, “0”}, m = 5, n = 3
Output: 4
Explanation: There are totally 4 strings can be formed by the using of
5 0s and 3 1s, which are “10”,“0001”,“1”,“0”
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
#构建矩阵的方式,与numpy反着来,numpy直接输入(len(strs),n,m),这里正好先定义最里层即numpy的[-1]的值,接着依次反向定义。
#最里层循环最快,之后调用最里面在最外面
dp = [[[0] *(n+1) for _ in range(m+1)] for _ in range(len(strs) + 1)]
for i in range(1, len(strs) + 1):
ones = strs[i-1].count("1")
zeros = strs[i-1].count("0")
for j in range(m+1):
for k in range(n+1):
#反着操作,不行就用numpy更直接
dp[i][j][k] = dp[i-1][j][k]
if j >= zeros and k >= ones:
dp[i][j][k] = max(dp[i][j][k],dp[i-1][j-zeros][k - ones] + 1)
return dp[-1][-1][-1]
优化一下,因为其实不需要k这个概念,只依靠上一个
class Solution: def findMaxForm(self, strs: List[str], m: int, n: int) -> int: if len(strs) == 0: return 0 dp = [[0] * (n+1) for _ in range(m+1)] for strs_item in strs: zeros = strs_item.count("0") ones = strs_item.count("1") #注意这里是倒着来的,因为是01背包 for i in range(m, zeros - 1, -1): for j in range(n, ones - 1, -1): dp[i][j] = max(dp[i][j], 1+dp[i- zeros][j-ones]) return dp[m][n] 作者:LeahChao 链接:https://leetcode-cn.com/problems/ones-and-zeroes/solution/bei-bao-wen-ti-zhuan-hua-zhu-bu-kong-jian-you-hua-/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
题目描述:给一些面额的硬币,要求用这些硬币来组成给定面额的钱数,并且使得硬币数量最少。硬币可以重复使用。
物品:硬币
物品大小:面额
物品价值:数量
Example 1:
coins = [1, 2, 5], amount = 11 return 3 (11 = 5 + 5 + 1)Example 2: coins = [2], amount = 3 return -1.

class Solution:
def coinChange(self,coins, amount):
dp=[float('inf') for _ in range(amount+1)]
dp[0] = 0
for i in range(len(coins)):
for j in range(coins[i],amount+1):
dp[j] = min(dp[j],dp[j-coins[i]]+1)
return dp[-1] if dp[-1] != float('inf') else -1
class Solution:
def change(self,amount,coins):
if len(coins) == 0 and amount==0:
return 1
elif len(coins) == 0 and amount!=0:
return 0
dp = [0]*(amount+1)
dp[0] = 1
for coin in coins:
for j in range(coin,amount+1):
#类似斐波那契,有合适的就拆出来
dp[j]+=dp[j-coin]
return dp[-1]
回溯解法:
class Solution: def change(self,amount, coins): def dfs(start_index, amount, coins,res): if amount == 0: res[0]+=1 return if amount<0: return for i in range(start_index,len(coins)): dfs(i,amount-coins[i],coins,res) res = [0] start_index = 0 dfs(start_index, amount, coins, res) return res[0] #如果变动index则是每个元素只能取一次 def solution_wrong(self, amount, coins): ans = [0] def backtrack(coins, index, amount, ans): if amount == 0: ans[0] += 1 return if amount < 0: return for i in range(index, len(coins)): backtrack(coins, index+1, amount - coins[i], ans) backtrack(coins, 0, amount, ans) return ans[0]
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。
输入: s = “applepenapple”, wordDict = [“apple”, “pen”] 输出: true 解释: 返回
true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。
输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false
class Solution:
def wordBreak(self, s, wordDict):
return self._wordBreak(s, set(wordDict), 0)
def _wordBreak(self, s, words, start):
if start == len(s):
return True
for i in range(start + 1, len(s) + 1):
sub = s[start:i]
if sub in words and self._wordBreak(s, words, i):
return True
return False
回溯,一致性思路解法:
class Solution: ''' 之前的想法是顺序思考,不断增长字符串,反向思考,不断去除列表中字符串长度, 直到正好长度为0 ''' def _wordBreak(self, s, words, index): if 0 == len(s): self.ans[0] = True return for i in range(index,len(words)): if s.startswith(words[i]): self._wordBreak(s[len(words[i]):], words, index) def wordBreak(self, s, wordDict): self.ans = [False] self._wordBreak(s, wordDict, 0) return self.ans[0]
(2)dp
仔细想还真是背包的问题。我们遍历到第一个"aaaa",此时我们要知道f(7)是否成立,我们只需要知道f(3)是否可以成立即可,如果f(3)成立,我们这个时候只需要将"aaaa"放入即可。而我们需要知道f(3)是否可以成立,我们就需要直到f(-1)能否成立,但是-1超出了边界,所以我们判断"aaa"能否放入,也就是我们判断f(0)能否成立即可。而我们直到对于空字符串来说,直到我们单词字典中不选出单词即可,所以f(0)=True。所以按照这个过程,我们可以很快写出下面的代码。
而且是有序背包的话则要求后遍历物品,与一般背包不同。
class Solution:
def wordBreak(self, s, wordDict):
len_s = len(s)
mem = [False]*(len_s+1)
mem[0] = True
for i in range(1, len_s + 1):
for word in wordDict:
if i >= len(word) and mem[i - len(word)] \
and word == s[i-len(word):i]:
mem[i] = True
return mem[-1]
属于比较特殊的dp,dp内存储以该点为左下角的正方形的最长边的边数,dp的转移方程为dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1。即考虑该点为上面点由最薄弱的部分决定。
matrix = [ [0,1,1,1], [1,1,1,1], [0,1,1,1] ] 只由0,1构成,求由1构成的正方形的最大面积
- 1
- 2
- 3
- 4
- 5
- 6
- 7
class Solution:
def maximalSquare(self,matrix):
dp = [[0 for _ in range(len(matrix[0]))] for _ in range(len(matrix))]
maxvec = 0
for i in range(len(matrix)):
for j in range(len(matrix[i])):
if matrix[i][j] == '1':
if i == 0 or j == 0:
dp[i][j] = 1
else:
dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
maxvec = max(dp[i][j],maxvec)
return maxvec**2
matrix = [ [0,1,1,1], [1,1,1,1], [0,1,1,1] ] dp= [ [0,1,1,1], [1,1,2,2], [0,1,2,3] ]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
class Solution:
def countSquare(self,matrix):
dp = [[0 for _ in range(len(matrix[0]))] for _ in range(len(matrix))]
count = 0
for i in range(matrix):
for j in range(matrix[i]):
if matrix[i][j] == 1:
if i == 0 or j == 0:
dp[i][j] = 1
else:
dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
count += dp[i][j]
return count
删除最少的字符,让两个串相同。
(1).递归
class Solution: def minDistance(self, word1: str, word2: str) -> int: n1, n2 = len(word1), len(word2) def dfs(i,j): if i == n1: #如果第一个串走到尽头,则剩下第二个串剩余的部分为最后的剩余长度 return n2-j if j == n2: return n1-i res = float('inf') if word1[i] == word2[j]: res = min(res,dfs(i+1,j+1)) else: res = min(res,1+min(dfs(i+1,j),dfs(i,j+1)) return res
(2)dp
思路是找到删除最短的字符个数,那之前的最长序列是相同的,用补集的思路不就ok了吗。
class Solution: def minDistance(self, word1: str, word2: str) -> int: n1 = len(word1) n2 = len(word2) if n1 == 0: return n2 if n2 == 0: return n1 dp = [[0] * (n2 + 1) for _ in range(n1 + 1)] for i in range(n1 + 1): for j in range(n2 + 1): if i == 0 or j == 0: continue elif word1[i - 1] == word2[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) # print(dp[-1][-1]) max_num = dp[-1][-1] return (n1 + n2)- 2 * max_num
插入,删除,替换,使之变成一样的。(这个中英文都能使用,要中文要重新定义一下,这个题目做nlp的必须会,不会就要背下来。
)

class Solution: def minDistance(self, word1, word2): n1, n2 = len(word1), len(word2) # 初始化 dp 数组 dp = [[0]*(n2+1) for _ in range(n1+1)] for i in range(n1+1): # 第 0 列 dp[i][0] = i for j in range(n2+1): # 第 0 行 dp[0][j] = j # 更新 dp 数组 for i in range(1, n1+1): for j in range(1, n2+1): #先用字符判断,而后更改dp if word1[i-1] == word2[j-1]: dp[i][j] = dp[i-1][j-1] else: dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1 return dp[n1][n2]
dp[i][j] 代表 word1 到 i 位置转换成 word2 到 j 位置需要最少步数。
当 word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];当 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
其中,dp[i-1][j-1] 表示替换操作,dp[i-1][j] 表示删除操作,dp[i][j-1] 表示插入操作。
def divPrime(num):
lt = []
while num != 1:
for i in range(2, int(num + 1)):
if num % i == 0: # i是num的一个质因数
lt.append(i)
num = num / i # 将num除以i,剩下的部分继续分解
break
return sum(lt)
class Solution: def isPalinedrome(self,s,l,r): while l<r: if s[l] != s[r]: return False l+=1 r-=1 return True def longestPalindrome(self, s: str) -> str: length = len(s) if length==0: return '' best='' for i in range(length): for j in range(i+1,length): if j-i+1>len(best) and self.isPalinedrome(s,i,j): best = s[i:j+1] if best=='': return s[0] return best class Solution: def longestPalindrome(self, s: str) -> str: # s[i]==s[j] --> dp[i][j] = dp[i+1][j-1] 这样写就不会越界,让j在外面,i在里面,本来就不分先后 if not s: return '' length = len(s) # dp = [[False]*length]*length dp = [[False for _ in range(length)] for _ in range(length)] max_length = 1 start_index = 0 for i in range(length): dp[i][i] = True # ababa for j in range(1, length): for i in range(j): if s[i] == s[j]: if j - i + 1 < 3: dp[i][j] = True else: dp[i][j] = dp[i + 1][j - 1] if dp[i][j]: if max_length < j - i + 1: max_length = j - i + 1 start_index = i return s[start_index:max_length + start_index]
1.递归和动态规划都是将原问题拆成多个子问题然后求解,先思考一下通项公式是什么,一般可以按照需要把前面都遍历一遍,只需要依靠前几个就可以,或者是都不是。
2.问题复杂可以按照递归先想出来思路,然后完全逆向的写dp。
3.双if的思路。
4.递归返回值。
5.max里带上自己本身可以从自己开始另开一个新篇章。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。