diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md index 2ca15726..5111e30e 100644 --- a/problems/0062.不同路径.md +++ b/problems/0062.不同路径.md @@ -287,17 +287,70 @@ public: ``` ### Python - +递归 ```python -class Solution: # 动态规划 +class Solution: def uniquePaths(self, m: int, n: int) -> int: - dp = [[1 for i in range(n)] for j in range(m)] + if m == 1 or n == 1: + return 1 + return self.uniquePaths(m - 1, n) + self.uniquePaths(m, n - 1) + +``` +动态规划(版本一) +```python +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + # 创建一个二维列表用于存储唯一路径数 + dp = [[0] * n for _ in range(m)] + + # 设置第一行和第一列的基本情况 + for i in range(m): + dp[i][0] = 1 + for j in range(n): + dp[0][j] = 1 + + # 计算每个单元格的唯一路径数 for i in range(1, m): for j in range(1, n): - dp[i][j] = dp[i][j - 1] + dp[i - 1][j] + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + + # 返回右下角单元格的唯一路径数 return dp[m - 1][n - 1] -``` +``` +动态规划(版本二) +```python +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + # 创建一个一维列表用于存储每列的唯一路径数 + dp = [1] * n + + # 计算每个单元格的唯一路径数 + for j in range(1, m): + for i in range(1, n): + dp[i] += dp[i - 1] + + # 返回右下角单元格的唯一路径数 + return dp[n - 1] +``` +数论 +```python +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + numerator = 1 # 分子 + denominator = m - 1 # 分母 + count = m - 1 # 计数器,表示剩余需要计算的乘积项个数 + t = m + n - 2 # 初始乘积项 + while count > 0: + numerator *= t # 计算乘积项的分子部分 + t -= 1 # 递减乘积项 + while denominator != 0 and numerator % denominator == 0: + numerator //= denominator # 约简分子 + denominator -= 1 # 递减分母 + count -= 1 # 计数器减1,继续下一项的计算 + return numerator # 返回最终的唯一路径数 + +``` ### Go ```Go diff --git a/problems/0063.不同路径II.md b/problems/0063.不同路径II.md index b2713774..7b5b44f0 100644 --- a/problems/0063.不同路径II.md +++ b/problems/0063.不同路径II.md @@ -271,69 +271,130 @@ class Solution { ### Python - +动态规划(版本一) ```python class Solution: - def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int: - # 构造一个DP table - row = len(obstacleGrid) - col = len(obstacleGrid[0]) - dp = [[0 for _ in range(col)] for _ in range(row)] - dp[0][0] = 0 if obstacleGrid[0][0] == 1 else 1 - if dp[0][0] == 0: - return 0 # 如果第一个格子就是障碍,return 0 - # 第一行 - for i in range(1, col): - if obstacleGrid[0][i] == 1: - # 遇到障碍物时,直接退出循环,后面默认都是0 + def uniquePathsWithObstacles(self, obstacleGrid): + m = len(obstacleGrid) + n = len(obstacleGrid[0]) + if obstacleGrid[m - 1][n - 1] == 1 or obstacleGrid[0][0] == 1: + return 0 + dp = [[0] * n for _ in range(m)] + for i in range(m): + if obstacleGrid[i][0] == 0: # 遇到障碍物时,直接退出循环,后面默认都是0 + dp[i][0] = 1 + else: break - dp[0][i] = 1 - - # 第一列 - for i in range(1, row): - if obstacleGrid[i][0] == 1: - # 遇到障碍物时,直接退出循环,后面默认都是0 + for j in range(n): + if obstacleGrid[0][j] == 0: + dp[0][j] = 1 + else: break - dp[i][0] = 1 - # print(dp) + for i in range(1, m): + for j in range(1, n): + if obstacleGrid[i][j] == 1: + continue + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + return dp[m - 1][n - 1] - for i in range(1, row): - for j in range(1, col): - if obstacleGrid[i][j] == 0: - dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - return dp[-1][-1] ``` +动态规划(版本二) +```python +class Solution: + def uniquePathsWithObstacles(self, obstacleGrid): + m = len(obstacleGrid) # 网格的行数 + n = len(obstacleGrid[0]) # 网格的列数 + + if obstacleGrid[m - 1][n - 1] == 1 or obstacleGrid[0][0] == 1: + # 如果起点或终点有障碍物,直接返回0 + return 0 + + dp = [[0] * n for _ in range(m)] # 创建一个二维列表用于存储路径数 + + # 设置起点的路径数为1 + dp[0][0] = 1 if obstacleGrid[0][0] == 0 else 0 + + # 计算第一列的路径数 + for i in range(1, m): + if obstacleGrid[i][0] == 0: + dp[i][0] = dp[i - 1][0] + + # 计算第一行的路径数 + for j in range(1, n): + if obstacleGrid[0][j] == 0: + dp[0][j] = dp[0][j - 1] + + # 计算其他位置的路径数 + for i in range(1, m): + for j in range(1, n): + if obstacleGrid[i][j] == 1: + continue + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + + return dp[m - 1][n - 1] # 返回终点的路径数 + + +``` +动态规划(版本三) ```python class Solution: - """ - 使用一维dp数组 - """ + def uniquePathsWithObstacles(self, obstacleGrid): + if obstacleGrid[0][0] == 1: + return 0 + + dp = [0] * len(obstacleGrid[0]) # 创建一个一维列表用于存储路径数 + + # 初始化第一行的路径数 + for j in range(len(dp)): + if obstacleGrid[0][j] == 1: + dp[j] = 0 + elif j == 0: + dp[j] = 1 + else: + dp[j] = dp[j - 1] - def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int: + # 计算其他行的路径数 + for i in range(1, len(obstacleGrid)): + for j in range(len(dp)): + if obstacleGrid[i][j] == 1: + dp[j] = 0 + elif j != 0: + dp[j] = dp[j] + dp[j - 1] + + return dp[-1] # 返回最后一个元素,即终点的路径数 + +``` +动态规划(版本四) + +```python +class Solution: + def uniquePathsWithObstacles(self, obstacleGrid): + if obstacleGrid[0][0] == 1: + return 0 + m, n = len(obstacleGrid), len(obstacleGrid[0]) - - # 初始化dp数组 - # 该数组缓存当前行 - curr = [0] * n + + dp = [0] * n # 创建一个一维列表用于存储路径数 + + # 初始化第一行的路径数 for j in range(n): if obstacleGrid[0][j] == 1: break - curr[j] = 1 + dp[j] = 1 - for i in range(1, m): # 从第二行开始 - for j in range(n): # 从第一列开始,因为第一列可能有障碍物 - # 有障碍物处无法通行,状态就设成0 + # 计算其他行的路径数 + for i in range(1, m): + if obstacleGrid[i][0] == 1: + dp[0] = 0 + for j in range(1, n): if obstacleGrid[i][j] == 1: - curr[j] = 0 - elif j > 0: - # 等价于 - # dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - curr[j] = curr[j] + curr[j - 1] - # 隐含的状态更新 - # dp[i][0] = dp[i - 1][0] + dp[j] = 0 + else: + dp[j] += dp[j - 1] + + return dp[-1] # 返回最后一个元素,即终点的路径数 - return curr[n - 1] ``` diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md index 793d3e67..1b24e491 100644 --- a/problems/0070.爬楼梯.md +++ b/problems/0070.爬楼梯.md @@ -251,32 +251,66 @@ class Solution { ``` ### Python - +动态规划(版本一) ```python # 空间复杂度为O(n)版本 class Solution: def climbStairs(self, n: int) -> int: - # dp[i] 为第 i 阶楼梯有多少种方法爬到楼顶 - dp = [0]*(n+1) - dp[0] = 1 + if n <= 1: + return n + + dp = [0] * (n + 1) dp[1] = 1 - for i in range(2, n+1): - dp[i] = dp[i-1] + dp[i-2] + dp[2] = 2 + + for i in range(3, n + 1): + dp[i] = dp[i - 1] + dp[i - 2] + return dp[n] +``` +动态规划(版本二) +```python + +# 空间复杂度为O(3)版本 +class Solution: + def climbStairs(self, n: int) -> int: + if n <= 1: + return n + + dp = [0] * 3 + dp[1] = 1 + dp[2] = 2 + + for i in range(3, n + 1): + total = dp[1] + dp[2] + dp[1] = dp[2] + dp[2] = total + + return dp[2] + +``` +动态规划(版本三) +```python + # 空间复杂度为O(1)版本 class Solution: def climbStairs(self, n: int) -> int: - dp = [0]*(n+1) - dp[0] = 1 - dp[1] = 1 - for i in range(2,n+1): - tmp = dp[0] + dp[1] - dp[0] = dp[1] - dp[1] = tmp - return dp[1] -``` + if n <= 1: + return n + + prev1 = 1 + prev2 = 2 + + for i in range(3, n + 1): + total = prev1 + prev2 + prev1 = prev2 + prev2 = total + + return prev2 + +``` ### Go ```Go func climbStairs(n int) int { diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md index 9f41906d..368a5747 100644 --- a/problems/0096.不同的二叉搜索树.md +++ b/problems/0096.不同的二叉搜索树.md @@ -197,12 +197,13 @@ class Solution { ```python class Solution: def numTrees(self, n: int) -> int: - dp = [0] * (n + 1) - dp[0], dp[1] = 1, 1 - for i in range(2, n + 1): - for j in range(1, i + 1): - dp[i] += dp[j - 1] * dp[i - j] - return dp[-1] + dp = [0] * (n + 1) # 创建一个长度为n+1的数组,初始化为0 + dp[0] = 1 # 当n为0时,只有一种情况,即空树,所以dp[0] = 1 + for i in range(1, n + 1): # 遍历从1到n的每个数字 + for j in range(1, i + 1): # 对于每个数字i,计算以i为根节点的二叉搜索树的数量 + dp[i] += dp[j - 1] * dp[i - j] # 利用动态规划的思想,累加左子树和右子树的组合数量 + return dp[n] # 返回以1到n为节点的二叉搜索树的总数量 + ``` ### Go diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index ddd6f162..d70641db 100644 --- a/problems/0343.整数拆分.md +++ b/problems/0343.整数拆分.md @@ -245,20 +245,68 @@ class Solution { ``` ### Python +动态规划(版本一) ```python class Solution: - def integerBreak(self, n: int) -> int: - dp = [0] * (n + 1) - dp[2] = 1 + # 假设对正整数 i 拆分出的第一个正整数是 j(1 <= j < i),则有以下两种方案: + # 1) 将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j * (i-j) + # 2) 将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j * dp[i-j] + def integerBreak(self, n): + dp = [0] * (n + 1) # 创建一个大小为n+1的数组来存储计算结果 + dp[2] = 1 # 初始化dp[2]为1,因为当n=2时,只有一个切割方式1+1=2,乘积为1 + + # 从3开始计算,直到n for i in range(3, n + 1): - # 假设对正整数 i 拆分出的第一个正整数是 j(1 <= j < i),则有以下两种方案: - # 1) 将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j * (i-j) - # 2) 将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j * dp[i-j] + # 遍历所有可能的切割点 for j in range(1, i // 2 + 1): - dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j])) - return dp[n] -``` + # 计算切割点j和剩余部分(i-j)的乘积,并与之前的结果进行比较取较大值 + + dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j)) + + return dp[n] # 返回最终的计算结果 + +``` +动态规划(版本二) +```python +class Solution: + def integerBreak(self, n): + if n <= 3: + return 1 * (n - 1) # 对于n小于等于3的情况,返回1 * (n - 1) + + dp = [0] * (n + 1) # 创建一个大小为n+1的数组来存储最大乘积结果 + dp[1] = 1 # 当n等于1时,最大乘积为1 + dp[2] = 2 # 当n等于2时,最大乘积为2 + dp[3] = 3 # 当n等于3时,最大乘积为3 + + # 从4开始计算,直到n + for i in range(4, n + 1): + # 遍历所有可能的切割点 + for j in range(1, i // 2 + 1): + # 计算切割点j和剩余部分(i - j)的乘积,并与之前的结果进行比较取较大值 + dp[i] = max(dp[i], dp[i - j] * dp[j]) + + return dp[n] # 返回整数拆分的最大乘积结果 + +``` +贪心 +```python +class Solution: + def integerBreak(self, n): + if n == 2: # 当n等于2时,只有一种拆分方式:1+1=2,乘积为1 + return 1 + if n == 3: # 当n等于3时,只有一种拆分方式:1+1+1=3,乘积为1 + return 2 + if n == 4: # 当n等于4时,有两种拆分方式:2+2=4和1+1+1+1=4,乘积都为4 + return 4 + result = 1 + while n > 4: + result *= 3 # 每次乘以3,因为3的乘积比其他数字更大 + n -= 3 # 每次减去3 + result *= n # 将剩余的n乘以最后的结果 + return result + +``` ### Go ```go func integerBreak(n int) int { diff --git a/problems/0509.斐波那契数.md b/problems/0509.斐波那契数.md index 479b36a0..7ace0723 100644 --- a/problems/0509.斐波那契数.md +++ b/problems/0509.斐波那契数.md @@ -203,18 +203,9 @@ class Solution { ``` ### Python +动态规划(版本一) ```python -class Solution: - def fib(self, n: int) -> int: - if n < 2: - return n - a, b, c = 0, 1, 0 - for i in range(1, n): - c = a + b - a, b = b, c - return c -# 动态规划 (注释版。无修饰) class Solution: def fib(self, n: int) -> int: @@ -238,7 +229,48 @@ class Solution: # 返回答案 return dp[n] -# 递归实现 +``` +动态规划(版本二) +```python + +class Solution: + def fib(self, n: int) -> int: + if n <= 1: + return n + + dp = [0, 1] + + for i in range(2, n + 1): + total = dp[0] + dp[1] + dp[0] = dp[1] + dp[1] = total + + return dp[1] + + +``` +动态规划(版本三) +```python +class Solution: + def fib(self, n: int) -> int: + if n <= 1: + return n + + prev1, prev2 = 0, 1 + + for _ in range(2, n + 1): + curr = prev1 + prev2 + prev1, prev2 = prev2, curr + + return prev2 + + + + +``` +递归(版本一) +```python + class Solution: def fib(self, n: int) -> int: if n < 2: diff --git a/problems/0746.使用最小花费爬楼梯.md b/problems/0746.使用最小花费爬楼梯.md index 31e7bd48..cd5e40e6 100644 --- a/problems/0746.使用最小花费爬楼梯.md +++ b/problems/0746.使用最小花费爬楼梯.md @@ -245,26 +245,42 @@ class Solution { ``` ### Python + +动态规划(版本一) ```python -# 第一步不支付费用 -class Solution: - def minCostClimbingStairs(self, cost: List[int]) -> int: - n = len(cost) - dp = [0]*(n+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[-1] -``` -```python -# 第一步支付费用 + class Solution: def minCostClimbingStairs(self, cost: List[int]) -> int: dp = [0] * (len(cost) + 1) - dp[0] = 0 - dp[1] = 0 + dp[0] = 0 # 初始值,表示从起点开始不需要花费体力 + dp[1] = 0 # 初始值,表示经过第一步不需要花费体力 + for i in range(2, len(cost) + 1): - dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i-2]) - return dp[len(cost)] + # 在第i步,可以选择从前一步(i-1)花费体力到达当前步,或者从前两步(i-2)花费体力到达当前步 + # 选择其中花费体力较小的路径,加上当前步的花费,更新dp数组 + dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]) + + return dp[len(cost)] # 返回到达楼顶的最小花费 + +``` +动态规划(版本二) + +```python +class Solution: + def minCostClimbingStairs(self, cost: List[int]) -> int: + dp0 = 0 # 初始值,表示从起点开始不需要花费体力 + dp1 = 0 # 初始值,表示经过第一步不需要花费体力 + + for i in range(2, len(cost) + 1): + # 在第i步,可以选择从前一步(i-1)花费体力到达当前步,或者从前两步(i-2)花费体力到达当前步 + # 选择其中花费体力较小的路径,加上当前步的花费,得到当前步的最小花费 + dpi = min(dp1 + cost[i - 1], dp0 + cost[i - 2]) + + dp0 = dp1 # 更新dp0为前一步的值,即上一次循环中的dp1 + dp1 = dpi # 更新dp1为当前步的最小花费 + + return dp1 # 返回到达楼顶的最小花费 + ``` ### Go diff --git a/problems/0968.监控二叉树.md b/problems/0968.监控二叉树.md index fdec56ca..e2ba8ebf 100644 --- a/problems/0968.监控二叉树.md +++ b/problems/0968.监控二叉树.md @@ -371,56 +371,104 @@ class Solution { ### Python - +贪心(版本一) ```python class Solution: - def minCameraCover(self, root: TreeNode) -> int: - # Greedy Algo: + # Greedy Algo: # 从下往上安装摄像头:跳过leaves这样安装数量最少,局部最优 -> 全局最优 # 先给leaves的父节点安装,然后每隔两层节点安装一个摄像头,直到Head # 0: 该节点未覆盖 # 1: 该节点有摄像头 # 2: 该节点有覆盖 + def minCameraCover(self, root: TreeNode) -> int: + # 定义递归函数 + result = [0] # 用于记录摄像头的安装数量 + if self.traversal(root, result) == 0: + result[0] += 1 - result = 0 - # 从下往上遍历:后序(左右中) - def traversal(curr: TreeNode) -> int: - nonlocal result + return result[0] - if not curr: return 2 - left = traversal(curr.left) - right = traversal(curr.right) + + def traversal(self, cur: TreeNode, result: List[int]) -> int: + if not cur: + return 2 - # Case 1: - # 左右节点都有覆盖 - if left == 2 and right == 2: - return 0 + left = self.traversal(cur.left, result) + right = self.traversal(cur.right, result) - # Case 2: - # left == 0 && right == 0 左右节点无覆盖 - # left == 1 && right == 0 左节点有摄像头,右节点无覆盖 - # left == 0 && right == 1 左节点有无覆盖,右节点摄像头 - # left == 0 && right == 2 左节点无覆盖,右节点覆盖 - # left == 2 && right == 0 左节点覆盖,右节点无覆盖 - elif left == 0 or right == 0: - result += 1 - return 1 + # 情况1: 左右节点都有覆盖 + if left == 2 and right == 2: + return 0 - # Case 3: - # left == 1 && right == 2 左节点有摄像头,右节点有覆盖 - # left == 2 && right == 1 左节点有覆盖,右节点有摄像头 - # left == 1 && right == 1 左右节点都有摄像头 - elif left == 1 or right == 1: - return 2 + # 情况2: + # left == 0 && right == 0 左右节点无覆盖 + # left == 1 && right == 0 左节点有摄像头,右节点无覆盖 + # left == 0 && right == 1 左节点无覆盖,右节点有摄像头 + # left == 0 && right == 2 左节点无覆盖,右节点覆盖 + # left == 2 && right == 0 左节点覆盖,右节点无覆盖 + if left == 0 or right == 0: + result[0] += 1 + return 1 - # 其他情况前段代码均已覆盖 + # 情况3: + # left == 1 && right == 2 左节点有摄像头,右节点有覆盖 + # left == 2 && right == 1 左节点有覆盖,右节点有摄像头 + # left == 1 && right == 1 左右节点都有摄像头 + if left == 1 or right == 1: + return 2 - if traversal(root) == 0: - result += 1 - return result ``` +贪心(版本二)利用elif精简代码 +```python +class Solution: + # Greedy Algo: + # 从下往上安装摄像头:跳过leaves这样安装数量最少,局部最优 -> 全局最优 + # 先给leaves的父节点安装,然后每隔两层节点安装一个摄像头,直到Head + # 0: 该节点未覆盖 + # 1: 该节点有摄像头 + # 2: 该节点有覆盖 + def minCameraCover(self, root: TreeNode) -> int: + # 定义递归函数 + result = [0] # 用于记录摄像头的安装数量 + if self.traversal(root, result) == 0: + result[0] += 1 + return result[0] + + + def traversal(self, cur: TreeNode, result: List[int]) -> int: + if not cur: + return 2 + + left = self.traversal(cur.left, result) + right = self.traversal(cur.right, result) + + # 情况1: 左右节点都有覆盖 + if left == 2 and right == 2: + return 0 + + # 情况2: + # left == 0 && right == 0 左右节点无覆盖 + # left == 1 && right == 0 左节点有摄像头,右节点无覆盖 + # left == 0 && right == 1 左节点无覆盖,右节点有摄像头 + # left == 0 && right == 2 左节点无覆盖,右节点覆盖 + # left == 2 && right == 0 左节点覆盖,右节点无覆盖 + elif left == 0 or right == 0: + result[0] += 1 + return 1 + + # 情况3: + # left == 1 && right == 2 左节点有摄像头,右节点有覆盖 + # left == 2 && right == 1 左节点有覆盖,右节点有摄像头 + # left == 1 && right == 1 左右节点都有摄像头 + else: + return 2 + + + + +``` ### Go ```go