leetcode 300.最长递增子序列 | 刷题打卡

92 阅读3分钟

题目链接

解法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] + 1dp[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中的最大值。

图解

image-20220427123144495

代码

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…