动态规划

509. 斐波那契数

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 01 开始,后面的每一项数字都是前面两项数字的和。也就是:

1
2
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n)

示例 1:

1
2
3
输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

1
2
3
输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

1
2
3
输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

提示:

  • 0 <= n <= 30
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def fib(self, n: int) -> int:
if n == 0 :
return 0
if n == 1 :
return 1
b2 = 0
b1 = 1
for i in range(2, n+1):
res = b2 + b1
b2 = b1
b1 = res
return res

70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

1
2
3
4
5
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶

示例 2:

1
2
3
4
5
6
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

提示:

  • 1 <= n <= 45
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def climbStairs(self, n: int) -> int:
if n ==1 :
return 1

dp = [1] * (n)
dp[0] = 1
dp[1] = 2

for i in range(2, n):
dp[i] = dp[i-1] + dp[i-2]

return dp[n-1]

746. 使用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

示例 1:

1
2
3
4
5
输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。
- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15 。

示例 2:

1
2
3
4
5
6
7
8
9
10
输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。

提示:

  • 2 <= cost.length <= 1000
  • 0 <= cost[i] <= 999
1
2
3
4
5
6
7
8
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
dp = [0] * (len(cost)+1)

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)]

62. 不同路径

一个机器人位于一个 m x n网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:

1
2
输入:m = 3, n = 7
输出:28

示例 2:

1
2
3
4
5
6
7
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下

示例 3:

1
2
输入:m = 7, n = 3
输出:28

示例 4:

1
2
输入:m = 3, n = 3
输出:6

提示:

  • 1 <= m, n <= 100
  • 题目数据保证答案小于等于 2 * 109
1
2
3
4
5
6
7
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
matrix = [[1 for i in range(n)] for j in range(m)]
for i in range(1, m):
for j in range(1, n):
matrix[i][j] = matrix[i-1][j] + matrix[i][j-1]
return matrix[m-1][n-1]

63. 不同路径 II

给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角(即 grid[0][0])。机器人尝试移动到 右下角(即 grid[m - 1][n - 1])。机器人每次只能向下或者向右移动一步。

网格中的障碍物和空位置分别用 10 来表示。机器人的移动路径中不能包含 任何 有障碍物的方格。

返回机器人能够到达右下角的不同路径数量。

测试用例保证答案小于等于 2 * 109

示例 1:

1
2
3
4
5
6
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

示例 2:

1
2
输入:obstacleGrid = [[0,1],[0,0]]
输出:1

提示:

  • m == obstacleGrid.length
  • n == obstacleGrid[i].length
  • 1 <= m, n <= 100
  • obstacleGrid[i][j]01
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m = len(obstacleGrid)
n = len(obstacleGrid[0])
matrix = [[0 for i in range(n)] for j in range(m)]
#第一行
for j in range(n):
if obstacleGrid[0][j] ==0:
matrix[0][j] = 1
else:
break
#第一列
for i in range(m):
if obstacleGrid[i][0] ==0:
matrix[i][0] = 1
else:
break

for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j] != 1:
matrix[i][j] = matrix[i-1][j] + matrix[i][j-1]
return matrix[m-1][n-1]

343. 整数拆分

给定一个正整数 n ,将其拆分为 k正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积 。

示例 1:

1
2
3
输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

示例 2:

1
2
3
输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

提示:

  • 2 <= n <= 58
1
2
3
4
5
6
7
8
9
10
11
class Solution:
def integerBreak(self, n: int) -> int:
dp = [0] * (n + 1)
dp[1] = 1
dp[2] = 1

for i in range(3, n+1):
for j in range(1, i):
# dp[i]找最大,一个拆一个,一个不拆
dp[i] = max(dp[i], j*dp[i-j], j*(i-j))
return dp[n]

96. 不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例 1:

1
2
输入:n = 3
输出:5

示例 2:

1
2
输入:n = 1
输出:1

提示:

  • 1 <= n <= 19
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def numTrees(self, n: int) -> int:
if n == 1 :
return 1
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
dp[2] = 2
for i in range(3, n+1):
for j in range(i):
dp[i] += dp[j]*dp[i-j-1]
# print(dp)
return dp[n]

416. 分割等和子集

给你一个 只包含正整数非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

1
2
3
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

1
2
3
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution:
def canPartition(self, nums: List[int]) -> bool:
if sum(nums) % 2 !=0:
return False
target = sum(nums) // 2
dp = [0] * (sum(nums) // 2 +1)
# 先物品
for i in range(len(nums)):
# 再对背包生成dp数组
# 只有大于i才能装
for j in range(target, nums[i]-1, -1):
dp[j] = max(dp[j], dp[j-nums[i]] + nums[i])


# 装满target正好
if dp[target] == target:
return True
return False

1049. 最后一块石头的重量 II

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0

示例 1:

1
2
3
4
5
6
7
输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。

示例 2:

1
2
输入:stones = [31,26,33,21,40]
输出:5

提示:

  • 1 <= stones.length <= 30
  • 1 <= stones[i] <= 100
1
2
3
4
5
6
7
8
9
10
11
class Solution:
def lastStoneWeightII(self, stones: List[int]) -> int:
target = sum(stones) // 2

dp = [0] * (target + 1)

for i in range(len(stones)):
for j in range(len(dp) -1, stones[i] -1, -1 ):
dp[j] = max(dp[j], dp[j-stones[i]] + stones[i] )

return sum(stones)-2*dp[target]

494. 目标和

给你一个非负整数数组 nums 和一个整数 target

向数组中的每个整数前添加 '+''-' ,然后串联起所有整数,可以构造一个 表达式

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1"

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

1
2
3
4
5
6
7
8
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-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 = 3

示例 2:

1
2
输入:nums = [1], target = 1
输出:1

提示:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums[i]) <= 1000
  • -1000 <= target <= 1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
if (target + sum(nums)) % 2 !=0:
return 0
if abs(sum(nums)) < abs(target):
return 0
bagSize = (target + sum(nums)) // 2
dp = [0]*(bagSize+1)
# 什么都不要,也是方案
dp[0]=1
for i in range(len(nums)):
for j in range(bagSize, nums[i]-1, -1):
# 不放i有dp[i-1][j]种, 放i有dp[i-1][j-nums[i]]种
dp[j] = dp[j] + dp[j-nums[i]]
return dp[bagSize]

474. 一和零

给你一个二进制字符串数组 strs 和两个整数 mn

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y子集

示例 1:

1
2
3
4
输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2:

1
2
3
输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。

提示:

  • 1 <= strs.length <= 600
  • 1 <= strs[i].length <= 100
  • strs[i] 仅由 '0' 和 '1' 组成
  • 1 <= m, n <= 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
dp = [[[0 for _ in range(n+1)] for _ in range(m+1)] for _ in range(len(strs))]

# 初始化顶层
# for mi in range(m):
# dp[0][m][0] = 1 if len(strs[0]) == m and '1' not in strs[0] else 0

# for ni in range(n):
# dp[0][0][n] = 1 if len(strs[0]) == n and '0' not in strs[0] else 0

# 计算第一层01
n0 = 0
n1 = 0
for char in strs[0]:
if char=='0':
n0+=1
else:
n1+=1
for mi in range(m+1):
for ni in range(n+1):
dp[0][mi][ni] = 1 if mi >= n0 and ni >= n1 else 0

# 纵向一定是0


# 开始遍历,物品先
for i in range(1,len(strs)):
# 计算01
n0 = 0
n1 = 0
for char in strs[i]:
if char=='0':
n0+=1
else:
n1+=1
for mi in range(m+1):
for ni in range(n+1):
# 能拿
if mi>=n0 and ni>=n1:
dp[i][mi][ni] = max(dp[i-1][mi][ni], dp[i-1][mi-n0][ni-n1]+1)
# 不能拿
else:
dp[i][mi][ni] = dp[i-1][mi][ni]

# for i in range(len(dp)):
# print(f"i = {i}")
# for line in dp[i]:
# print(line)
return dp[len(strs)-1][m][n]

518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

1
2
3
4
5
6
7
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

1
2
3
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

1
2
输入:amount = 10, coins = [10]
输出:1

提示:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0] * (amount + 1)
dp[0] = 1
# dp数组:dp[j]表示j块钱有几种方案

for i in range(len(coins)):
for j in range(amount+1):
if j >= coins[i]:
dp[j] = dp[j] + dp[j-coins[i]]

print(dp)
return dp[amount]

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素排列的个数。

题目数据保证答案符合 32 位整数范围。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

示例 2:

1
2
输入:nums = [9], target = 3
输出:0

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • nums 中的所有元素 互不相同
  • 1 <= target <= 1000

**进阶:**如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
# dp数组
# dp[j] 表示j的组合总数
dp = [0] * (target +1)
dp[0] = 1

# 先物品算的是排列
for j in range(1, target+1):
for i in range(len(nums)):
if j >= nums[i]:
dp[j] = dp[j] + dp[j-nums[i]]

print(dp)
return dp[target]

322. 零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

示例 1:

1
2
3
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

示例 2:

1
2
输入:coins = [2], amount = 3
输出:-1

示例 3:

1
2
输入:coins = [1], amount = 0
输出:0

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
# dp[j]为最少个数
dp = [inf] * (amount + 1)
dp[0] = 0


for j in range(amount+1):
for i in range(len(coins)):
# 每轮遍历硬币
if j >= coins[i]:
dp[j] = min(dp[j], dp[j-coins[i]]+1)

# print(dp)
return dp[amount] if dp[amount] is not inf else -1

279. 完全平方数

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,14916 都是完全平方数,而 311 不是。

示例 1:

1
2
3
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4

示例 2:

1
2
3
输入:n = 13
输出:2
解释:13 = 4 + 9

提示:

  • 1 <= n <= 104
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def numSquares(self, n: int) -> int:
dp = [inf for _ in range(n+1)]
dp[0] = 0
dp[1] = 1

for i in range(n+1):
base = 1
while base * base <= i:
dp[i] = min(dp[i-base*base]+1, dp[i])
base += 1

# print(dp)
return dp[n]

139. 单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true

**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

1
2
3
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。

示例 2:

1
2
3
4
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
  注意,你可以重复使用字典中的单词。

示例 3:

1
2
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

提示:

  • 1 <= s.length <= 300
  • 1 <= wordDict.length <= 1000
  • 1 <= wordDict[i].length <= 20
  • swordDict[i] 仅由小写英文字母组成
  • wordDict 中的所有字符串 互不相同
1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
# dp[i]是长度为n的字串能不能拆
dp = [False] * (len(s)+1)
dp[0] = True

for j in range(len(s)+1):
for i in range(len(wordDict)):
if j >= len(wordDict[i]):
dp[j] = dp[j] or (dp[j-len(wordDict[i])] and s[j-len(wordDict[i]):j] == wordDict[i])

return dp[len(s)]

198. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

1
2
3
4
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
  偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

1
2
3
4
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
  偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400
1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) == 1:
return nums[0]
dp = [0 for _ in range(len(nums))]
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])

for i in range(2, len(nums)):
dp[i] = max(dp[i-2]+nums[i], dp[i-1])

return dp[len(nums)-1]

213. 打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

1
2
3
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

1
2
3
4
输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
  偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

1
2
输入:nums = [1,2,3]
输出:3

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums)==1:
return nums[0]
# dp0数组表示偷0,且n房的前n房最大
dp0 = [0]*(len(nums))
dp0[0] = nums[0]
dp0[1] = max(nums[0],nums[1])

for i in range(2, len(nums)-1):
# 偷、不偷
dp0[i] = max(dp0[i-2]+nums[i], dp0[i-1])
dp0[len(nums)-1] = dp0[len(nums)-2]

# dp1数组表示不偷0,且n房的前n房最大
dp1 = [0]*(len(nums))
dp1[0] = 0
dp1[1] = nums[1]

for i in range(2, len(nums)):
# 偷、不偷
dp1[i] = max(dp1[i-2]+nums[i], dp1[i-1])

# print(dp0,dp1)

return max(dp0[len(nums)-1],dp1[len(nums)-1])

337. 打家劫舍 III

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

示例 1:

1
2
3
输入: root = [3,2,3,null,3,null,1]
输出: 7
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7

示例 2:

1
2
3
输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

提示:

  • 树的节点数在 [1, 104] 范围内
  • 0 <= Node.val <= 104
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: Optional[TreeNode]) -> int:
return self.dfs(root)[0]


def dfs(self,root):
if root is None:
return 0, 0, 0

leftDP, leftleftDP, leftrightDP = self.dfs(root.left)
rightDP, rightleftDP, rightrightDP = self.dfs(root.right)

rootDP = max(leftleftDP + leftrightDP + rightleftDP + rightrightDP + root.val, leftDP + rightDP)
return rootDP, leftDP, rightDP

121. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 1:

1
2
3
4
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

1
2
3
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 104
1
2
3
4
5
6
7
8
class Solution:
def maxProfit(self, prices: List[int]) -> int:
minN = inf
res = 0
for price in prices:
minN = min(minN, price)
res = max(res, price - minN)
return res

122. 买卖股票的最佳时机 II

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。然而,你可以在 同一天 多次买卖该股票,但要确保你持有的股票不超过一股。

返回 你能获得的 最大 利润 。

示例 1:

1
2
3
4
5
输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3。
最大总利润为 4 + 3 = 7 。

示例 2:

1
2
3
4
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
最大总利润为 4 。

示例 3:

1
2
3
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 104
  • 0 <= prices[i] <= 104

123. 买卖股票的最佳时机 III

给定一个数组,它的第i 个元素是一支给定的股票在第 i天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔交易。

**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

1
2
3
4
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
  随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。

示例 2:

1
2
3
4
5
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
  注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
  因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

1
2
3
输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。

示例 4:

1
2
输入:prices = [1]
输出:0

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 105
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# dp[i][][0]表示i天不持有k
# dp[i][k][1]表示i天持有k
# k是完成了多少交易
k = 2
dp = [[[-inf, -inf] for _ in range(k+1)] for _ in range(len(prices))]
dp[0][0][0] = 0
for kidx in range(k+1):
dp[0][kidx][0] = 0
dp[0][0][1] = -prices[0]

for i in range(1, len(prices)):
for kidx in range(k+1):
# print(f"{i=}, {kidx=}")
# 卖/不买
if kidx > 0:
dp[i][kidx][0] = max(dp[i-1][kidx-1][1]+prices[i], dp[i-1][kidx][0])
else:
dp[i][kidx][0] = dp[i-1][kidx][0]
# 买/不卖
dp[i][kidx][1] = max(dp[i-1][kidx][0]-prices[i], dp[i-1][kidx][1])

# for line in dp:
# print(line)
return max([dp[len(prices)-1][kidx][0] for kidx in range(k+1)])

188. 买卖股票的最佳时机 IV

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

1
2
3
输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。

示例 2:

1
2
3
4
输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。

提示:

  • 1 <= k <= 100
  • 1 <= prices.length <= 1000
  • 0 <= prices[i] <= 1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
# dp[i][][0]表示i天不持有k
# dp[i][k][1]表示i天持有k
# k是完成了多少交易
# k = 2
dp = [[[-inf, -inf] for _ in range(k+1)] for _ in range(len(prices))]
dp[0][0][0] = 0
for kidx in range(k+1):
dp[0][kidx][0] = 0
dp[0][0][1] = -prices[0]

for i in range(1, len(prices)):
for kidx in range(k+1):
# print(f"{i=}, {kidx=}")
# 卖/不买
if kidx > 0:
dp[i][kidx][0] = max(dp[i-1][kidx-1][1]+prices[i], dp[i-1][kidx][0])
else:
dp[i][kidx][0] = dp[i-1][kidx][0]
# 买/不卖
dp[i][kidx][1] = max(dp[i-1][kidx][0]-prices[i], dp[i-1][kidx][1])

# for line in dp:
# print(line)
return max([dp[len(prices)-1][kidx][0] for kidx in range(k+1)])

309. 买卖股票的最佳时机含冷冻期

给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

1
2
3
输入: prices = [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

示例 2:

1
2
输入: prices = [1]
输出: 0

提示:

  • 1 <= prices.length <= 5000
  • 0 <= prices[i] <= 1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# dp是第i天持有股票
# dp[i][0]不持有,dp[i][1]持有, dp[i][2]冷冻
dp = [[-inf, -inf, -inf] for _ in range(len(prices))]
dp[0][0] = 0
dp[0][1] = -prices[0]

for i in range(1, len(prices)):
# 不买,冷却结束
dp[i][0] = max(dp[i-1][0], dp[i-1][2])
# 买,不卖
dp[i][1] = max(dp[i-1][0]-prices[i], dp[i-1][1])
# 卖
dp[i][2] = dp[i-1][1]+prices[i]

# print(dp)
return max(dp[len(prices)-1][0], dp[len(prices)-1][2])

714. 买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

**注意:**这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

示例 1:

1
2
3
4
5
6
7
8
输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
解释:能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8

示例 2:

1
2
输入:prices = [1,3,7,5,10,3], fee = 3
输出:6

提示:

  • 1 <= prices.length <= 5 * 104
  • 1 <= prices[i] < 5 * 104
  • 0 <= fee < 5 * 104
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
# dp是第i天持有股票
# dp[i][0]不持有,dp[i][1]持有,
dp = [[-inf, -inf] for _ in range(len(prices))]
dp[0][0] = 0
dp[0][1] = -prices[0]

for i in range(1, len(prices)):
# 不买,卖
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]-fee)
# 买,不卖
dp[i][1] = max(dp[i-1][0]-prices[i], dp[i-1][1])

# print(dp)
return dp[len(prices)-1][0]

300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

1
2
3
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

1
2
输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

1
2
输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104

进阶:

  • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?
1
2
3
4
5
6
7
8
9
10
11
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
# dp[i]以i结尾的最长子序列
dp = [1] * len(nums)

for i in range(1, len(nums)):
for j in range(i):
if nums[i]>nums[j]:
dp[i] = max(dp[i], dp[j]+1)
# print(dp)
return max(dp)

674. 最长连续递增序列

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 lrl < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

示例 1:

1
2
3
4
输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。

示例 2:

1
2
3
输入:nums = [2,2,2,2,2]
输出:1
解释:最长连续递增序列是 [2], 长度为1。

提示:

  • 1 <= nums.length <= 104
  • -109 <= nums[i] <= 109
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
if len(nums) ==1:
return 1

start = 0
end = 1
maxL = 1

while end < len(nums):
# print(start, end)
if nums[end] > nums[end-1]:
end+=1
else:
if end-start>maxL:
maxL = end-start
start = end
end = end +1

if end-start>maxL:
maxL = end-start


return maxL

718. 最长重复子数组

给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度

示例 1:

1
2
3
输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1] 。

示例 2:

1
2
输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
输出:5

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
# dp[i]表示以i为结尾的重复子数组
dp = [[0 for _ in range(len(nums2))] for _ in range(len(nums1)) ]
for i in range(len(nums1)):
dp[i][0] = 1 if nums1[i] == nums2[0] else 0
for j in range(len(nums2)):
dp[0][j] = 1 if nums2[j] == nums1[0] else 0

for i in range(1, len(nums1)):
for j in range(1, len(nums2)):
# i = j / i!= j
if nums1[i] == nums2[j]:
dp[i][j] = dp[i-1][j-1]+1
else:
dp[i][j] = 0 # max(dp[i-1][j], dp[i][j-1])

maxN = -inf
for line in dp:
# print(line)
maxline = max(line)
if maxline > maxN:
maxN = maxline
return maxN

1143. 最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0

一个字符串的 子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,"ace""abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

1
2
3
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。

示例 2:

1
2
3
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。

示例 3:

1
2
3
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。

提示:

  • 1 <= text1.length, text2.length <= 1000
  • text1 和 text2 仅由小写英文字符组成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
dp = [[0 for _ in range(len(text2))] for _ in range(len(text1))]

dp[0][0] = 1 if text1[0]==text2[0] else 0

for i in range(1, len(text1)):
dp[i][0] = max(dp[i-1][0], int(text1[i]==text2[0]))

for j in range(len(text2)):
dp[0][j] = max(dp[0][j-1], int(text2[j]==text1[0]))

for i in range(1, len(text1)):
for j in range(1, len(text2)):
if text1[i] == text2[j]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])

# for line in dp:
# print(line)
return dp[len(text1)-1][len(text2)-1]

1035. 不相交的线

在两条独立的水平线上按给定的顺序写下 nums1nums2 中的整数。

现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足:

  • nums1[i] == nums2[j]
  • 且绘制的直线不与任何其他连线(非水平线)相交。

请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。

以这种方法绘制线条,并返回可以绘制的最大连线数。

示例 1:

1
2
3
4
输入:nums1 = [1,4,2], nums2 = [1,2,4]
输出:2
解释:可以画出两条不交叉的线,如上图所示。
但无法画出第三条不相交的直线,因为从 nums1[1]=4 到 nums2[2]=4 的直线将与从 nums1[2]=2 到 nums2[1]=2 的直线相交。

示例 2:

1
2
输入:nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2]
输出:3

示例 3:

1
2
输入:nums1 = [1,3,7,1,7,5], nums2 = [1,9,2,5,1]
输出:2

提示:

  • 1 <= nums1.length, nums2.length <= 500
  • 1 <= nums1[i], nums2[j] <= 2000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
def maxUncrossedLines(self, nums1: List[int], nums2: List[int]) -> int:
# dp[i][j]表示以ij结尾的最大数
dp = [[0 for _ in range(len(nums2))] for _ in range(len(nums1))]

dp[0][0] = 1 if nums1[0] == nums2[0] else 0

for i in range(1, len(nums1)):
dp[i][0] = 1 if nums1[i] == nums2[0] or dp[i-1][0] == 1 else 0

for j in range(1, len(nums2)):
dp[0][j] = 1 if nums2[j] == nums1[0] or dp[0][j-1] == 1 else 0

for i in range(1, len(nums1)):
for j in range(1, len(nums2)):
if nums1[i] == nums2[j]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])

# for line in dp:
# print(line)
return dp[len(nums1)-1][len(nums2)-1]

53. 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

1
2
3
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

1
2
输入:nums = [1]
输出:1

示例 3:

1
2
输入:nums = [5,4,-1,7,8]
输出:23

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

**进阶:**如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
res = -inf
part = 0
for num in nums:
if part < 0:
part = num
else:
part += num
res = max(res, part)
return res

392. 判断子序列

给定字符串 st ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

进阶:

如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

致谢:

特别感谢@pbrother添加此问题并且创建所有测试用例。

示例 1:

1
2
输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

1
2
输入:s = "axc", t = "ahbgdc"
输出:false

提示:

  • 0 <= s.length <= 100
  • 0 <= t.length <= 10^4
  • 两个字符串都只由小写字符组成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if len(s) ==0:
return True
if len(t) ==0:
return False
# dp[i][j]表示以i为结束是否为子序列
dp = [[False for _ in range(len(t))] for _ in range(len(s))]

dp[0][0] = True if s[0]==t[0] else False

for i in range(1, len(s)):
dp[i][0] = False

for j in range(1, len(t)):
dp[0][j] = t[j] == s[0] or dp[0][j-1]

for i in range(1, len(s)):
for j in range(1, len(t)):
if s[i] == t[j]:
dp[i][j] = dp[i-1][j-1] and s[i] == t[j]
else:
dp[i][j] = dp[i][j-1]


# for line in dp:
# print(line)
return dp[len(s)-1][len(t)-1]

115. 不同的子序列

给你两个字符串 st ,统计并返回在 s子序列t 出现的个数。

测试用例保证结果在 32 位有符号整数范围内。

示例 1:

1
2
3
4
5
6
7
输入:s = "rabbbit", t = "rabbit"
输出:3
解释:
如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
rabbbit
rabbbit
rabbbit

示例 2:

1
2
3
4
5
6
7
8
9
输入:s = "babgbag", t = "bag"
输出:5
解释:
如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。
babgbag
babgbag
babgbag
babgbag
babgbag

提示:

  • 1 <= s.length, t.length <= 1000
  • st 由英文字母组成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution:
def numDistinct(self, s: str, t: str) -> int:
# dp[i][j]表示以i为结束是否为子序列
dp = [[0 for _ in range(len(t)+1)] for _ in range(len(s)+1)]

dp[0][0] = 1 if s[0]==t[0] else 0

for i in range(1, len(s)+1):
dp[i][0] = 1

for j in range(1, len(t)+1):
dp[0][j] = 0


for j in range(1, len(t)+1):
for i in range(1, len(s)+1):
# 用不用
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
else:
dp[i][j] = dp[i-1][j]


# for line in dp:
# print(line)
return dp[len(s)][len(t)]

583. 两个字符串的删除操作

给定两个单词 word1 和 word2 ,返回使得 word1 和  word2相同所需的最小步数

每步可以删除任意一个字符串中的一个字符。

示例 1:

1
2
3
输入: word1 = "sea", word2 = "eat"
输出: 2
解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"

示例  2:

1
2
输入:word1 = "leetcode", word2 = "etco"
输出:4

提示:

  • 1 <= word1.length, word2.length <= 500
  • word1 和 word2 只包含小写英文字母
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
# dp[i][j]表示i,j字串拆完要多少步
dp = [[0 for _ in range(len(word2)+1) ] for _ in range(len(word1)+1) ]

for i in range(1, len(word1)+1):
dp[i][0] = i
for j in range(1, len(word2)+1):
dp[0][j] = j

for i in range(1, len(word1)+1):
for j in range(1, len(word2)+1):
# 相等不删
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j-1] + 2, dp[i-1][j] + 1, dp[i][j-1]+1)

# for line in dp:
# print(line)

return dp[len(word1)][len(word2)]

72. 编辑距离

给你两个单词 word1 和 word2请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:

1
2
3
4
5
6
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

1
2
3
4
5
6
7
8
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

提示:

  • 0 <= word1.length, word2.length <= 500
  • word1word2 由小写英文字母组成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
# dp表示i-1和j-1的最下编辑距离
dp = [[0 for _ in range(len(word2)+1)] for _ in range(len(word1)+1)]

for i in range(1, len(word1)+1):
dp[i][0] = i
for j in range(1, len(word2)+1):
dp[0][j] = j

for i in range(1, len(word1)+1):
for j in range(1, len(word2)+1):
# 相等
# if i == j:
# if word1[i-1] == word2[j-1]:
# dp[i][j] = dp[i-1][j-1]
# else:
# dp[i][j] = dp[i-1][j-1] + 1
# else:
# if word1[i-1] == word2[j-1]:
# dp[i][j] = dp[i-1][j-1]
# else:
# 改、删、加
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j-1] + 1, dp[i-1][j] +1 , dp[i][j-1] +1 )

# for line in dp:
# print(line)

return dp[len(word1)][len(word2)]

647. 回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

示例 1:

1
2
3
输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"

示例 2:

1
2
3
输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"

提示:

  • 1 <= s.length <= 1000
  • s 由小写英文字母组成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def countSubstrings(self, s: str) -> int:
if len(s) == 1:
return 1
# dp表示前i字符的回文数

dp = [0] * len(s)

dp[0]=1

for i in range(len(s)):
temp = 0
for j in range(i+1):
if s[j:i] == s[i:j:-1]:
temp += 1
dp[i] = dp[i-1] + temp

# print(dp)
return dp[len(s)-1]

516. 最长回文子序列

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

1
2
3
输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

示例 2:

1
2
3
输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。

提示:

  • 1 <= s.length <= 1000
  • s 仅由小写英文字母组成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
# dp[i][j]表示[i,j]最长回文串l(j>=i)
# 需要复习
dp = [[0 for j in range(len(s))] for i in range(len(s))]

for i in range(len(s)):
dp[i][i] = 1

for i in range(len(s)-1, -1, -1):
for j in range(i+1, len(s)):
# 四种情况:都不要,加左,加右,都加
# 都加
if s[i]==s[j]:
dp[i][j] = dp[i+1][j-1] + 2
# 这里很巧妙
else:
dp[i][j] = max(dp[i+1][j], dp[i][j-1])

# for line in dp:
# print(line)
return max(max(dp))