动态规划主要解决的问题种类有:
解决步骤:
class Solution:
def fib(self, n: int) -> int:
if n==0:
return 0
if n==1:
return 1
dp = [0]*35
dp[1] = 1
for i in range(2,31):
dp[i] = dp[i-1]+dp[i-2]
return dp[n]
效率:0ms,击败100.00%
再优化一下,因为每个斐波那契数只和它相邻的两个数有关,所以我们其实不需要存储三十多个长度,只需要保留2个数的信息即可。也就是状态压缩。
class Solution:
def fib(self, n: int) -> int:
if n==0:
return 0
if n==1:
return 1
dp = [0]*2
dp[1]=1
sum = 0
for i in range(2,n):
sum = dp[0]+dp[1]
dp[0] = dp[1]
dp[1] = sum
return dp[0]+dp[1]
跟509.斐波那契数列
很像
class Solution:
def climbStairs(self, n: int) -> int:
if n==1:
return 1
if n==2:
return 2
dp = [0] * 50
dp[1] = 1
dp[2] = 2
for i in range(3,n+1):
dp[i] = dp[i-1]+dp[i-2]
print(dp)
return dp[n]
效率:0ms,击败100.00%
class Solution:
def climbStairs(self, n: int) -> int:
if n==1:
return 1
if n==2:
return 2
dp = [0] * 4
dp[1] = 1
dp[2] = 2
sum = 0
for i in range(3,n):
sum = dp[1]+dp[2]
dp[1] = dp[2]
dp[2] = sum
return dp[1]+dp[2]
需要注意的是,这里的dp[i]
代表着爬到台阶为i
时所需的最小代价。
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
n = len(cost)
if n == 2:
return min(cost[0],cost[1])
dp = [0]*1005
dp[0] = 0
dp[1] = 0
for i in range(2,n+1):
dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
return dp[n]
效率:2ms,击败83.69%
d[-1][-1]
可以直接返回数组的右下角。而且因为只能向下或者向右,所以相当于第一行和第一列都是1
,所以初始化全都为1
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
if n == 1 or m == 1:
return 1
dp = [[1] * n for _ in range(m)]
for i in range(1,m):
for j in range(1,n):
dp[i][j]=dp[i-1][j]+dp[i][j-1]
return dp[-1][-1]
这道题的考点在于初始化的部分。不能像62.不同路径
一样全部初始化为0
或为1
,因为可能遇到以下情况:
0
。而前面的路线应该为1
。注意点:any()
是一个内置函数,它用于检查可迭代对象(如列表、元组、字符串、字典等)是否至少包含一个 True
值的元素。如果找到至少一个 True
值,any()
函数返回 True
;如果所有元素都是 False
或者可迭代对象为空,则返回 False
。
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m = len(obstacleGrid)
n = len(obstacleGrid[0])
if obstacleGrid[0][0] == 1:
# 初始台地有障碍物
return 0
if m == 1 or n == 1:
if any(1 in row for row in obstacleGrid):
# 只存在向下或者向右的一条路,还被石头挡住了
return 0
else:
return 1
dp = [[0]* n for _ in range(m)]
for i in range(1,m):
if obstacleGrid[i][0]==0:
dp[i][0] = 1
else:
# 说明这列遇到石头了,后面的路都应为0
break
for j in range(1,n):
if obstacleGrid[0][j]==0:
dp[0][j] = 1
else:
break
for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j] == 0:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]
效率:0ms,击败100.00%
注意:这里所有的边界条件提前判断都可以合并到第一行和第一列的处理当中,只需要把range(1,m)和range(0,n)
改为range(0,m)和range(0,n)
。这样代码量会少很多,但是效率就低了很多。如下:
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m = len(obstacleGrid)
n = len(obstacleGrid[0])
if obstacleGrid[0][0] == 1:
# 初始台地有障碍物
return 0
dp = [[0]* n for _ in range(m)]
for i in range(0,m):
if obstacleGrid[i][0]==0:
dp[i][0] = 1
else:
# 说明这列遇到石头了,后面的路都应为0
break
for j in range(0,n):
if obstacleGrid[0][j]==0:
dp[0][j] = 1
else:
break
for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j] == 0:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]
效率:3ms,击败25.15%
虽然这道题代码很短,但实际上值得做一下的。
首先我们需要明确一下dp[i]
的含义:总和为i
的两个或两个以上的数的乘积最大值。 也就是说dp[i]
可能是两个数的乘积,可能是三个或者以上的数的乘积。
前者,两个数的乘积,假设其中一个为k
,也就是dp[i]=k*(i-k)
如果是三个或者以上的数的乘积,我们先假设是三个数的乘积,其中一个为k
,一个为m
,也就是dp[i]=k*m*(i-k-m)
。需要注意的是,根据dp[i]
的定义,这又可以表示为dp[i]=(k+m)*dp[i-(k+m)]
,k+m
用n
表示,这个n
肯定比i
小,也就是说dp[i]=n*dp[i-n]
。同理,三个以上也可以用拆成两个乘数的dp形式表示。
所以dp[i]
本质上要找到是看dp[i]=k*(i-k)
和dp[i]=k*dp[i-k]
哪个大,这里的k
从1
遍历到i/2
即可。为什么呢?为了去重,而且最大值根据均值不等式,不会在后半部分因子出现。
class Solution:
def integerBreak(self, n: int) -> int:
dp = [0]*60
dp[2] = 1
for i in range(3,n+1):
for j in range(1,int(n/2)+1):
dp[i] = max(j*(i-j), j*dp[i-j], dp[i])
return dp[n]