解法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…