Merge pull request #2118 from jianghongcheng/master

Update 0968.监控二叉树.md
This commit is contained in:
程序员Carl
2023-06-03 21:31:19 +08:00
committed by GitHub
8 changed files with 433 additions and 140 deletions

View File

@ -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

View File

@ -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]
```

View File

@ -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 {

View File

@ -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

View File

@ -245,20 +245,68 @@ class Solution {
```
### Python
动态规划版本一
```python
class Solution:
def integerBreak(self, n: int) -> int:
dp = [0] * (n + 1)
dp[2] = 1
# 假设对正整数 i 拆分出的第一个正整数是 j1 <= j < i则有以下两种方案
# 1) 将 i 拆分成 j 和 ij 的和,且 ij 不再拆分成多个正整数,此时的乘积是 j * (i-j)
# 2) 将 i 拆分成 j 和 ij 的和,且 ij 继续拆分成多个正整数,此时的乘积是 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 拆分出的第一个正整数是 j1 <= j < i则有以下两种方案
# 1) 将 i 拆分成 j 和 ij 的和,且 ij 不再拆分成多个正整数,此时的乘积是 j * (i-j)
# 2) 将 i 拆分成 j 和 ij 的和,且 ij 继续拆分成多个正整数,此时的乘积是 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 {

View File

@ -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:

View File

@ -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

View File

@ -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