LeetCodeCampsDay43动态规划part10

使用动态规划解决子序列问题

非连续子序列可以看成模板,而连续子序列只是非连续子序列的特例

最长重复子序列需要使用二维dp(每个维度对应一个数组序列)

300. 最长递增子序列

https://leetcode.cn/problems/longest-increasing-subsequence/

给你一个整数数组 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)) 吗?

动态规划思路

本是和139.单词拆分有点儿像,

  1. dp数组下标与含义:

dp[i]表示第i位置的最长递增子序列长度

  1. 递推公式

需要有两个指针,一个i指向每个数字;另一个j范围[0~i),包含0不包含i,j指向0到nums[i - 1]之间的数字;

如果nums[i]大于nums[j],则dp[i]可以考虑更新其结果为:dp[i](上一轮的结果)与dp[j]+1中的最大值

dp[i] = max(dp[i], dp[j] + 1)

因为本题目不要求连续,所以可以从 让j从[0~i)(包含0不包含i)中抽数字

  1. dp初始化

全部初始化为1

  1. 遍历顺序

先用i遍历数字,再用j遍历[0~i),包含0不包含i

动态规划代码

  • 时间复杂度: O(n^2)
  • 空间复杂度: O(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
# 使用dp数组做
# dp数组下标与含义:dp[i][0]表示将字符i装入的最长长度,dp[i][1]表示将字符i不装入的最长长度

# 递推公式
# dp[i] = max(dp[i], dp[j] + 1)
L = len(nums)
if L <= 1:
return L
dp = [1] * L
res = 0
for i in range(1, L):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
if dp[i] > res:
res = dp[i]

return res

674. 最长连续递增序列

https://leetcode.cn/problems/longest-continuous-increasing-subsequence/

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

连续递增的子序列 可以由两个下标 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

动态规划思路

本题目与300.最长递增子序列不同点在于本题要求连续,

  1. dp数组下标与含义:

dp[i]表示第i位置的最长连续递增子序列长度

  1. 递推公式

因为本题目要求连续,“连续”意味着在使用j遍历时的起点需要修改成i - 1

需要有两个指针,一个i指向每个数字;另一个j的范围是[i - 1~i),包含i - 1不包含i,j指向0到nums[i - 1]之间的数字;

如果nums[i]大于nums[j],则dp[i]可以考虑更新其结果为:dp[i](上一轮的结果)与dp[j]+1中的最大值

dp[i] = max(dp[i], dp[j] + 1)

或者更直接一点儿

dp[i] = max(dp[i], dp[i - 1] + 1)

  1. dp初始化

全部初始化为1

  1. 遍历顺序

先用i遍历数字,再用j遍历[i - 1~i),包含i - 1不包含i

动态规划代码

  • 时间复杂度O(N)
  • 空间复杂度O(N)
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 findLengthOfLCIS(self, nums: List[int]) -> int:
# 使用dp解决
# dp下标与含义:dp[i]表示数字nums[i]的最长连续子序列长度

# 递推公式
# 如果nums[i] > nums[i - 1]
# dp[i] = max(dp[i], dp[i - 1]+1)

# 或者写成通用公式
# 令j的遍历范围为[i - 1, i),不包含i
# 递推公式为
# dp[i] = max(dp[i], dp[j] + 1)

# 初始化全为1
L = len(nums)
dp = [1] * L

res = 1
for i in range(1, L):
for j in range(i - 1, i):
if nums[i] > nums[i - 1]:
dp[i] = max(dp[i], dp[j] + 1)
res = max(dp[i], res)

return res

718. 最长重复子数组

https://leetcode.cn/problems/maximum-length-of-repeated-subarray/

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

示例 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. dp下标与定义,

dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。

(特别注意: “以下标i - 1为结尾的A” 标明一定是 以A[i-1]为结尾的字符串 )

  1. 递推公式

如果nums1[i] == nums2[j],则dp[i][j] = dp[i - 1][j - 1] + 1

如果不相等,则dp[i][j]就是0,因为它打破了连续性

  1. 初始化

dp[len(nums2) + 1][len(nums1) + 1], dp的必须强行添加一行一列,并且dp[i][0]和dp[0][j]全为0

因为dp[i][j] = dp[i - 1][j - 1]决定了i, j只能从下标1开始

  1. 遍历顺序

纵向对nums2,横向对nums1;把过来也行,但遍历顺序必须和dp初始化顺序一致

  1. 举例

img

img

以下所有非零值都由其位置的左上角加一得到的

x 0 1 1 1 1
x 0 0 0 0 0 0
1 0 0 1 1 1 1
0 0 1 0 0 0 0
1 0 0 2 1 1 1
0 0 1 0 0 0 0
1 0 0 2 1 1 1

动态规划代码

  • 时间复杂度O(N*M)
  • 空间复杂度O(N*M)
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
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
# dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。
# (特别注意: “以下标i - 1为结尾的A” 标明一定是 以A[i-1]为结尾的字符串 )

# 递推公式
# 如果nums1[i] == nums2[j],则dp[i][j] = dp[i - 1][j - 1] + 1
# 如果不相等,则dp[i][j]就是0,因为它打破了连续性

# 初始化
# dp[len(nums2) + 1][len(nums1) + 1]
# 并且第一列、第一行初始化为0
# 其它值也初始化为0

# 遍历顺序,纵向对nums2,横向对nums1

L1 = len(nums1)
L2 = len(nums2)
dp = [[0] * (L1 + 1) for _ in range(L2 + 1)]
# print(dp)
res = 0
#
for i in range(1, L2 + 1):
for j in range(1, L1 + 1):
if nums2[i - 1] == nums1[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
res = max(res, dp[i][j])

# print(dp)
return res