[动态规划、贪心算法] 最长递增子序列

本文介绍了解决最长递增子序列问题的两种方法:动态规划与贪心算法结合二分查找,通过实例展示了每种方法的具体实现过程,并对比了它们的时间与空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题

给定一个无序的整数数组,找到其中最长递增子序列的长度。

样例

第一个样例

输入:[ 10, 9, 2, 5, 3, 7, 101, 18 ]

输出:4

解释:最长的递增子序列是 [ 2, 3, 7, 101 ]

第二个样例

输入:[ 1, 2, 2, 3, 3 ]

输出:3

解释:最长的递增子序列是 [ 1, 2, 3 ]

解析

先仔细留意第二个样例,从输出和解释可以发现,题目要求的最长递增子序列必须是一个严格递增子序列!这个点可不能忽视!

提供两种思路:动态规划、贪心算法,贪心算法又可以配合二分查找算法进一步降低时间复杂度。

动态规划

动态规则最关键的解题思路就是能否找到状态转移方程。

本题中,定义 dp[i] 为前 i 个元素中,以第 i 个元素结尾的最长递增子序列的长度。那么,状态转移方程就是:

dp[i]=max(dp[0,\, i-1])+1 \: \: \: \: (\, 0 \leqslant j < i \: \: \: and\: \: \: num[j]<num[i]\, ) 

举个例子,假设以 num[i] 结尾的最长递增子序列长度是6,如果 num[i+1] > num[i],那么,以 num[i+1] 结尾的最长递增子序列长度就是 6+1=7

动态规划的时间复杂度是 O(n^2),空间复杂度是 O(n)

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

贪心算法 + 二分查找

贪心算法不太好理解,需要反复想象可能的场景。

先用语言描述一下贪心算法的思想,大概就是:如果想让递增子序列尽可能的长,就需要让序列上升的尽可能慢。

再用语言描述一下贪心算法的过程,大概就是:

  1. 创建一个临时数组,用于保存最长递增子序列
  2. 顺序遍历目标数组,尝试着将目标元素插入临时数组
  3. 如果目标元素比临时数组中所有元素都大,就把它追加到临时数组最后面
  4. 否则,就用目标元素替代临时数组中不小于目标值的最小元素

遍历完目标数组后,临时数组的长度就是求解的最长递增子序列的长度。但是!临时数组不一定就是最长递增子序列!

可以自己用一个数组走一遍算法流程,体验一下:[10,9,2,5,3,7,101,18,6,5,4,2,1,0]

能否理解这个贪心算法的过程,以及临时数组为什么不一定是最长递增子序列,就看个人的造化了。

说到这里,其实问题已经用贪心思路解决了,但是还有可优化的地方。优化点在临时数组的元素查找上面,虽然临时数组不一定是最长递增子序列,但是它一定是单调递增的,所以,查找元素可以用二分查找算法,降低这部分的时间复杂度。

贪心+二分查找的时间复杂度是O(nlogn),空间复杂度是O(n)

public int lengthOfLIS(int[] nums) {
    int length = nums.length;
    if (length <= 1) return length;
    int[] temp = new int[length];// 有可能的最长长度
    temp[0] = nums[0];// 至少有一个
    int index = 0;// 指向temp数组存储的最后一个元素
    for (int i = 1; i < length; i++) {
        int num = nums[i];
        if (num > temp[index]) temp[++index] = num;
        else {// 二分查找
            int left = 0, right = index;
            while (left < right) {
                int mid = (left + right) / 2;
                if (num > temp[mid]) left = mid + 1;
                else right = mid;
            }
            temp[left] = num;// 此时 left == right 仔细体会
        }
    }
    return index + 1;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值