这是我参与「第五届青训营 」伴学笔记创作活动的第 10 天
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
1. 解法一:动态规划
设以 nums[x] 为最后一个元素的最长递增子序列的长度为 len(x)。
则当 nums[j] < nums[i] 时,有 len(i) = Max(len(j)) + 1,其中 (0 <= j < i)。
若找不到一个 j (0 <= j < i),使 nums[j] < nums[i] 成立,则 len(i) = 1。
例如:
nums = [10,9,2,5,3,7,101,18]
len(0) = 1,[10]
len(1) = 1,[9]
len(2) = 1,[2]
len(3) = len(2)+1 = 2,[2,5]
len(4) = len(2)+1 = 2,[2,3]
len(5) = len(4)+1 = 3,[2,3,7] 或 len(3)+1 = 3,[2,5,7]
len(6) = len(5)+1 = 4,[2,3,7,101] 或 [2,5,7,101]
len(7) = len(5)+1 = 4,[2,3,7,18] 或 [2,5,7,18]
则整个数组的最长递增子序列的长度是 L = Max(len(i)),其中 (0 <= i < n)。
由上述讨论,有以下代码:
public int lengthOfLIS(int[] nums) {
// 整个数组的最长递增子序列的长度
int L = 0;
// 以 nums[x] 为最后一个元素的最长递增子序列的长度
int[] len = new int[nums.length];
for (int i = 0 ; i < nums.length; i++) {
len[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
if (len[i] < len[j] + 1) {
len[i] = len[j] + 1;
}
}
}
// 算完了 len[i]
if (L < len[i]) {
L = len[i];
}
}
return L;
}
时间复杂度 :1 + 2 + ... + n,n 为数组 nums 的长度。
空间复杂度 :需要额外的 len 数组,长度为 n。
2. 解法二:贪心 + 二分查找
2.1. 二分法寻找小于 target 的最右边的数
arr 是一个严格单调递增的有序数组。
binarySearchTheMin 返回小于 target 的最右边的数。
如果 arr 中的所有数都 大于或等于 target , 则返回 -1。
func binarySearchTheMin(arr []int, target int) int {
left := 0
right := len(arr) - 1
for left <= right {
mid := left + (right-left)/2
if arr[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return right
}
做单元测试:
func TestBinarySearchTheMin(t *testing.T) {
arr := []int{0, 1, 2, 3, 5, 5, 6}
testCases := []int{-1, 0, 1, 4, 5, 6, 7}
expected := []int{-1, -1, 0, 3, 3, 5, 6}
for i, testCase := range testCases {
pos := binarySearchTheMin(arr, testCase)
if expected[i] != pos {
t.Error("ERROR", pos, expected[i])
continue
}
if pos >= 0 && pos < len(arr) {
t.Log(pos, arr[pos], testCase)
} else {
t.Log(pos, "NaN", testCase)
}
}
}
输出如下,其中 target 为 -1 或 0 时,数组中所有的值都 不小于 (大于或等于) target 则返回 -1。
// === RUN TestBinarySearchTheMin
// pos arr[pos] target
// binary_search_test.go:23: -1 NaN -1
// binary_search_test.go:23: -1 NaN 0
// binary_search_test.go:21: 0 0 1
// binary_search_test.go:21: 3 3 4
// binary_search_test.go:21: 3 3 5
// binary_search_test.go:21: 5 5 6
// binary_search_test.go:21: 6 6 7
// --- PASS: TestBinarySearchTheMin (0.00s)
// PASS
2.2. 贪心算法
func lengthOfLIS(nums []int) int {
// 递增子序列
lis := make([]int, len(nums))
// 递增子序列 lis 的长度, 即 lis[0 : lis_len-1] 为递增子序列的有效范围
lis_len := 0
lis[lis_len] = nums[0]
lis_len++
for i := 1; i < len(nums); i++ {
if nums[i] > lis[lis_len-1] {
lis[lis_len] = nums[i]
lis_len++
} else {
pos := binarySearchTheMin(lis[0:lis_len], nums[i])
lis[pos+1] = nums[i]
}
//fmt.Println(lis[0:lis_len])
}
return lis_len
}
时间复杂度 : 遍历数组, 二分查找。
空间复杂度 :需要额外的 lis 数组,长度为 n。