您现在的位置是:首页 >其他 >【LeetCode 刷题】动态规划(1)-基础网站首页其他
【LeetCode 刷题】动态规划(1)-基础
简介【LeetCode 刷题】动态规划(1)-基础
此博客为《代码随想录》动态规划章节的学习笔记,主要内容为动态规划基础的相关题目解析。
509. 斐波那契数
class Solution:
def fib(self, n: int) -> int:
if n == 0:
return 0
dp = [0] * (n + 1)
dp[0], dp[1] = 0, 1
for i in range(2, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
- 状态定义:
dp[i]
的值代表 斐波那契数列第i
个数字 - 转移方程:
dp[i] = dp[i-1] + dp[i−2]
- 初始状态:
dp[0] = 0
,dp[1] = 1
,即初始化前两个数字 - 遍历顺序:从前到后
- 返回值:
dp[n]
,即斐波那契数列的第 n 个数字。
70. 爬楼梯
class Solution:
def climbStairs(self, n: int) -> int:
dp = [0] * (n + 1)
dp[0], dp[1] = 1, 1
for i in range(2, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
- 状态定义:
dp[i]
的值代表 到达第i
阶楼梯有多少种方案 - 初始值:
dp[0] = 0
,dp[1] = 1
,到达第 0、1 个阶梯都仅有一种方案 - 其余与上题相同
746. 使用最小花费爬楼梯
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
n = len(cost)
dp = [0] * (n + 1)
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]
- 状态定义:
dp[i]
的值代表 到达第i
阶楼梯的最少花费 - 初始值:
dp[0] = 0
,dp[1] = 0
,可以直接从下标为 0、1 的阶梯开始爬,因此最小花费为 0 - 转移方程:
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
62. 不同路径
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[0 for _ in range(n)] for _ in range(m)]
for i in range(m):
dp[i][0] = 1
for i in range(n):
dp[0][i] = 1
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[m-1][n-1]
- 状态定义:
dp[i][j]
的值代表 到达下标第(i, j)
网格有多少种路径 - 初始值:第一行、第一列 均为1
- 转移方程:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
另一种 dp 设置思路
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
dp[0][1] = 1
for i in range(1, m + 1):
for j in range(1, n + 1):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m][n]
- 状态定义:
dp[i][j]
的值代表 到达下标第(i - 1, j - 1)
网格有多少种路径,即外围添加了一圈 - 初始值:
dp[0][1] = 1
或者dp[1][0] = 1
,仅用于确保结果正确,无具体逻辑意义,其他额外添加的外围网格,均保持默认的初始化0
(省去了大量的初始赋值) - 返回值:
dp[m][n]
63. 不同路径 II
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
dp[0][1] = 1
for i in range(1, m + 1):
for j in range(1, n + 1):
if obstacleGrid[i-1][j-1] == 1:
dp[i][j] = 0
else:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m][n]
- 与上题第二种思路基本相同,仅状态转移时如果遇到障碍直接赋值为
0
- 注意:dp 数组与原始的网格的坐标之间存在 1 的偏差,因此判断的网格点为
obstacleGrid[i-1][j-1]
343. 整数拆分
class Solution:
def integerBreak(self, n: int) -> int:
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
for j in range(1, i):
dp[i] = max(dp[i], max(j * (i - j), j * dp[i-j]))
return dp[n]
- 状态定义:
dp[i]
的值代表 数i
划分后得到的最大乘积 - 初始化:
dp[1] = 1
(跳过dp[0]
) - 状态转移:
d
p
[
i
]
=
max
1
≤
j
<
i
{
max
(
j
×
(
i
−
j
)
,
j
×
d
p
[
i
−
j
]
)
}
dp[i]=max_{1≤j<i}{max(j×(i−j),j×dp[i−j])}
dp[i]=max1≤j<i{max(j×(i−j),j×dp[i−j])}
- 将
i
拆分成j
和i − j
的和,且i − j
不再拆分成多个正整数:i * (i−j)
- 将
i
拆分成j
和i − j
的和,且i − j
继续拆分成多个正整数:j * dp[i−j]
- 将
数学方法
class Solution:
def integerBreak(self, n: int) -> int:
if n <= 3: return n - 1
a, b = n // 3, n % 3
if b == 0: return int(math.pow(3, a))
if b == 1: return int(math.pow(3, a - 1) * 4)
return int(math.pow(3, a) * 2)
- 尽可能拆分成
3
n % 3 == 0
,全部拆成3
n % 3 == 1
,用一个3
搭配1
,拆成2 * 2
,其他全部拆成3
n % 3 == 2
,拆一个2
,其他全部拆成3
96. 不同的二叉搜索树
class Solution:
def numTrees(self, n: int) -> int:
dp = [0] * (n + 1)
dp[0] = 1
for i in range(1, n + 1):
for j in range(i):
dp[i] += dp[j] * dp[i-1-j]
return dp[n]
- 总节点为 n 的二叉搜索树,左右子树的节点数可能为(左,右):
(0, n - 1)
…(j, n - 1 - j)
…(n - 1, 0)
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。