解法1:动态规划
解题思路
-
当
nums
长度为0时,LIS(最长递增子序列)长度为0; -
当
nums
长度为1时,LIS长度为1; -
nums
长度为1时,LIS长度可能为1,可能为2。
nums
长度越长越包含的情况越多,但是可以拆分一下情况找出规律。
例如,当nums
长度为3时:
- 包含1项的LIS长度为1,这里使用
dp[0]
代替。 - 包含第2项的LIS长度,这里使用
dp[1]
代替,dp[1]
等于dp[0]
加1或者加0,因为dp[1]
比dp[0]
多了一项,这一项的值要么大于上一项的值,LIS加1,要么小于等于上一下的值,LIS不变。如当nums=[1, 2]
时,dp[1] = dp[0] + 1
为2,即[1, 2]
,当nums=[2, 1]
时,dp[1] = dp[0]
为1。 - 包含第3项的LIS长度,这里使用
dp[3]
代替,dp[3]
的值为dp[1] 或 dp[1] + 1
和dp[2] 或 dp[2] + 1
中大的那一个,其中dp[1]
加不加1,取决于nums[2]
是否大于nums[0]
,dp[2]
加不加1,取决于nums[2]
是否大于nums[0]
。
根据上面的分析可以等到,dp[n] = Math.max(dp[n], dp[0...n-1] 或 d[0...n-1] + 1)
,其中dp[n]
表式包含第n项时LIS长度。最后nums
的LIS就是dp
中的最大值。
图解
代码
TypeScript
function lengthOfLIS(nums: number[]): number {
const { length } = nums;
if (length === 0) {
return 0
}
const dp = new Array(length).fill(1);
let maxLen = 1;
for (let i = 1; i < length; i++) {
for (let j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLen = Math.max(maxLen, dp[i]);
}
return maxLen;
};
Rust
use std::cmp;
impl Solution {
pub fn length_of_lis(nums: Vec<i32>) -> i32 {
let len = nums.len();
if len == 0 {
0
} else {
let mut maxLen = 1;
let mut dp = nums.clone();
dp[0] = 1;
let mut i = 1;
while i < len {
dp[i] = 1;
let mut j = 0;
while j < i {
if nums[i] > nums[j] {
dp[i] = cmp::max(dp[i], dp[j] + 1);
}
j += 1;
}
maxLen = cmp::max(maxLen, dp[i]);
i += 1;
}
maxLen
}
}
}
解法2:贪心 + 二分查找
贪心就是让LIS增长的尽可能慢一些,这样能容纳更多的值。
假设有数组nums=[0, 8, 4, 12, 2]
:
-
第1步:初始化最长递增子序列
d
,插入0,d = [0]
; -
第2步:遇到8,8比0大,插入:
d = [0, 8]
; -
第3步:遇到4,4比8小,在
d
中进行二分查找,找到比4大一点的值8,替换:d = [0, 4]
; -
第4步:遇到12,12比4大,插入:
d = [0, 4, 12]
; -
第5步:遇到2,2比12小,在
d
中进行二分查找,找到比2大一点的值4,替换:d = [0, 2, 12]
;
最后得到LIS长度为3。
代码
TypeScript
function lengthOfLIS(nums: number[]): number {
const { length } = nums;
if (length === 0) {
return 0
}
const d = [nums[0]];
for (let i = 1; i < length; i++) {
const cur = nums[i];
const lastItem = d[d.length - 1];
// 当前值比最长递增子序列的最后一个值大,直接推入
if (cur > lastItem) {
d.push(cur);
} else {
// 二分查找
let l = 0;
let r = d.length - 1;
let pos = -1;
while (l <= r) {
const mid = (l + r) >> 1;
if (d[mid] < cur) {
pos = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
// 如果pos最后还等于 -1,证明找当前值比d中所有值都要小,替换d[0]
if (pos === -1) {
d[0] = cur;
} else {
d[pos + 1] = cur;
}
}
}
return d.length;
};
Rust
impl Solution {
pub fn length_of_lis(nums: Vec<i32>) -> i32 {
let len = nums.len();
if len == 0 {
0
} else {
let mut d: Vec<i32> = vec![nums[0]];
let mut i = 1;
while i < len {
let lastItem = d[d.len() - 1];
let cur = nums[i];
if cur > lastItem {
d.push(cur);
} else {
let mut l: i32 = 0;
let mut r: i32 = d.len() as i32;
let mut pos: i32 = -1;
while l <= r {
let mid = l + r >> 1;
if d[mid as usize] < cur {
pos = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
if pos == -1 {
d[0] = cur;
} else {
d[(pos + 1) as usize] = cur;
}
}
i += 1;
}
d.len() as i32
}
}
}
在vue3 diff源码中也使用到了最长递增子序列算法,感兴趣的可以查看这篇文章:juejin.cn/post/708035…