Loading

最长上升子序列(LIS)问题

300. 最长递增子序列

题目要求:给定整数数组nums,找到其中最长严格递增子序列的长度,子序列代表原序列中删除(不删除)数组中的元素而不改变其他元素的顺序。

解题思路

解法一:DFS+一维记忆化

  1. 从每个元素往下搜索满足的最长递增子序列长度
  2. 加一个缓存,记录已搜索过的起点,例如2->3->5,搜索起点为2的时候,起点为3的最长递增子序列也搜索了

​ 时间复杂度:O(n2) ,每个元素进行遍历后面的元素

​ 空间复杂度:O(n),即memo数组的占用空间,递归占用不会超过O(n)


class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if (n <= 1) {
            return n;
        }
        int[] memo = new int[n];
        int ans = 1;
        for (int i = 0; i < n; i++) { // 每个起点开始寻找
            // 已经搜过的起点跳过,因为最初标记的起点肯定最长
            if (memo[i] == 0) {
            	ans = Math.max(ans, dfs(i, nums, memo));
            }
        }
        return ans;
    }

    public int dfs(int index, int[] nums, int[] memo) {
        if (memo[index] != 0) {
            return memo[index];
        }
        int res = 1;
        for (int i = index + 1; i < nums.length; i++) { // 继续寻找比当前更大的元素
            if (nums[i] > nums[index]) {
                res = Math.max(res, 1 + dfs(i, nums, memo));
            }
        }
        memo[index] = res;
        return res;
    }
}

解法二:动态规划

  • 状态定义dp[i]表示以nums[i]结尾的最长递增子序列长度,这样在计算后面值的时候可以利用前面计算好的值

  • 状态转移讨论:设 j∈[0,i) ,每轮计算新 dp[i] 时,遍历 [0,i) ,做一下判断:

  1. 当 nums[i]>nums[j] 时: nums[i] 可以接到 nums[j] 的后面,满足严格递增,此时最长递增子序列长度为 dp[j]+1

  2. 当 nums[i] <= nums[j] 时: nums[i] 无法接到 nums[j] 的后面,不满足最长递增子序列,跳过

  3. 对于所有1情况计算出的 dp[j]+1 最大值,为直到 i 的最长递增子序列长度

  4. 转移方程: dp[i] = max(dp[i], dp[j] + 1) ,其中 j∈[0,i) , nums[i]>nums[j]

  • 初始状态: dp[i] 的所有元素为1,因为每个元素都可以称为子序列
  • 返回值:返回dp列表的最大值,即全局最长递增子序列长度

​ 时间复杂度:O(n2),每个元素都要遍历之前的元素

​ 空间复杂度:O(n),即dp数组占用空间

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if (n <= 1) {
            return n;
        }
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int res = 0;
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
                res = Math.max(res, dp[i]);
            }
        }
        return res;
    }
}

解法三:贪心解

​ 贪心思想:可以使用数组来记录之前出现最长的递增子序列,一旦出现比之前元素还小的元素时,把比之前大的元素给替换掉,以便容纳更多小些的元素,使得子序列递增的更缓慢一些。举个栗子,对于数组[0,1,0,3,2,3], 依次遍历,对应的记录数组变化

  1. i=0, g=[0],记录数组为空,直接加进来
  2. i=1, g=[0,1],比记录数组末尾的数大,接在后面
  3. i=2, g=[0,0],比记录数组中的数小,遍历找出对应位置替换
  4. i=3, g=[0,0,3],比记录数组末尾的数大,接在后面
  5. i=4, g=[0,0,2],比记录数组中的数小,遍历找出对应位置替换
  6. i=5,g=[0,0,2,3],比记录数组末尾的数大,接在后面
  7. 因此结果为记录数组的最大长度

​ 时间复杂度:O(n2)

​ 空间复杂度:O(n)

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if (n <= 1) {
            return n;
        }
        int[] g = new int[n];
        g[0] = nums[0];
        int size = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] > g[size - 1]) { // 接到上一个最长递增子序列的后面
                g[size++] = nums[i];
            } else {
                int r = size - 1;
                while (r >= 0 && g[r] >= nums[i]) { // 遍历找到比当前值小的索引
                    r--;
                }
                g[r + 1] = nums[i]; // 当前值小的数后面接上
            }
        }
        return size;
    }
}

解法四:贪心解+二分

​ 基于解法四进行优化,因为记录数组时递增的,因此适合在查找合适位置时使用二分查找

​ 时间复杂度:O(nlogn)

​ 空间复杂度:O(n)

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if (n <= 1) {
            return n;
        }
        int[] g = new int[n];
        g[0] = nums[0];
        int size = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] > g[size - 1]) {
                g[size++] = nums[i];
            } else {
                int l = 0, r = size - 1;
                while (l <= r) {
                    int mid = l + (r - l) / 2;
                    if (g[mid] >= nums[i]) { // 目标为左边界(查询大于等于nums[i]的值进行替换)
                        r = mid - 1;
                    } else {
                        l = mid + 1;
                    }
                }
                g[l] = nums[i]; // 左边界索引赋值
            }
        }
        return size;
    }
}
posted @ 2022-07-14 00:02  LogBiao  阅读(82)  评论(0编辑  收藏  举报