leetcode 300 的经典动态规划问题,求一个数组中最长递增子序列的长度。
$O(n^2)$的解法:
- dp数组元素dp[i]表示第i个元素参与构成的最长递增序列的长度;
- 状态转移方程:对于元素$j \subseteq [0, i-1] 且 nums[j] < nums[i]$,有 $dp[i] = max(dp[i], dp[j] + 1)$
- 初始化:每个元素都能构成一个长度为1的递增子序列,因此dp数组全部初始化为1
- 遍历顺序为从前往后扫描数组,每个数组元素再往前检查dp数组从中取最大值
据此可以写出第一版代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func lengthOfLIS(nums []int) int {
dp := make([]int, len(nums))
for i := range dp {
dp[i] = 1
}
ans := 1
for i := 1; i < len(dp); i++ {
for j := i - 1; j >= 0; j-- {
if nums[i] > nums[j] {
dp[i] = max(dp[i], dp[j] + 1)
}
}
ans = max(ans, dp[i])
}
return ans
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
上面解法的内层循环中,我们每次还要往前遍历数组元素来找下标i能参与构成的最长递增序列,似乎还有优化空间。事实上最优的解法是$O(n×log(n))$复杂度,下面来看这是如何做的。
- arr[i]定义为所有长度为i的递增子序列的最后一个元素中 最小的那个元素。比如对于数组[2,3,5],其中长度为2的子序列有
{2, 3}, {2, 5}, {3, 5}
,最后一个元素分别有5和6,因此$arr[2]=min{5, 6} = 5$。arr中保存的就是所有最长递增子序列中字典序最小的那个,一旦碰到比当前序列最后一个元素要大的元素,我们肯定就能构造出一个更长的递增序列。 - 在遍历原数组的过程中我们要不断更新arr中的元素,保持子序列是当前遇到过的所有序列中字典序最小的;因为arr中的元素是单调递增的,所以我们可以用二分查找来找到要更新的元素。
由此可以写出第二版$O(n×log(n))$的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func lengthOfLIS(nums []int) int {
dp := make([]int, len(nums))
dp[0] = nums[0]
size := 1
for i := 1; i < len(nums); i++ {
index := sort.Search(size, func (j int) bool { return dp[j] >= nums[i]})
if index == size {
dp[index] = nums[i]
size += 1
} else {
dp[index] = nums[i]
}
}
return size
}
这种解法应该不算动态规划了,只保存当前字典序最小的递增序列也许是一种贪心的思想。
下面我稍微修改一下题目,需要输出长度最长的递增子序列,如果不唯一则输出多个。
在这个题目要求下$O(n×log(n))$的解法应当是做不到的,我们还得使用第一版$O(n^2)$的做法。只要每次碰到长度等于ans的序列,就记录下来;如果碰到更长的子序列,就把之前存的全部丢弃,重新开始记录即可。
// TODO: needs update
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
func max(a, b int) int {
if a > b {
return a
}
return b
}
func updateRes(arr [][][]int, oldLen, num int) [][][]int {
index := oldLen - 1
for _, oldRow := range arr[index] {
if oldRow[len(oldRow) - 1] < num { // TODO: remove duplicate
newSeq := append(oldRow, []int{num}...)
arr[index + 1] = append(arr[index + 1], newSeq)
}
}
return arr
}
func lengthOfLIS(nums []int) [][]int {
dp := make([]int, len(nums))
dpRes := make([][][]int, len(nums))
for i := range nums {
dp[i] = 1
dpRes[0] = append(dpRes[0], [][]int...)
}
ans := 1
for i := 1; i < len(dp); i++ {
for j := i - 1; j >= 0; j-- {
if nums[i] > nums[j] {
dp[i] = max(dp[i], dp[j] + 1)
dpRes = updateRes(dpRes, dp[j], nums[i])
}
}
ans = max(ans, dp[i])
}
return dpRes[ans-1]
}