leetcode----300.最长递增子序列

300.最长递增子序列

问题:给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

思路:

  • 动态规划

第一步,定义dp数组的含义。定义dp[i]表示以第i个元素结尾的最长递增子序列的长度。

第二步,确定状态转移方程。假设需要求以第i个元素结尾的最长递增子序列的长度,即dp[i]。此时我们需要找到前i个元素中,对应元素值小于第i个元素值,且以这个元素结尾的最长递增子序列长度最大,再在这个子序列末尾加上第i个元素nums[i]。我们可以写出这样的状态转移方程。
d p [ i ] = m a x ( d p [ j ] ) + 1 , 0 ≤ j ≤ i a n d n u m s [ i ] > n u m s [ j ] dp[i]=max(dp[j])+1, \quad 0\leq j \leq i \quad and \quad nums[i] > nums[j] dp[i]=max(dp[j])+1,0jiandnums[i]>nums[j]

第三步,初始化dp数组,dp[0]=1,这个很容易知道,另外,由于我只会在nums[i] > nums[j]时更新dp[i]的值,所以再寻找最大的dp[j]时,会先令dp[i]=1,若前i个元素中不存在递增的子序列,dp[i]=1,若有则会更新dp[i]的值。防止出现dp[i]=0的情况。

最终所求的结果为 m a x ( d p [ i ] ) 0 ≤ i ≤ n u m s . l e n g t h max(dp[i]) \quad 0\leq i \leq nums.length max(dp[i])0inums.length.

class Solution {
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        if(nums.length == 0 || nums.length == 1) return nums.length;

        int[] dp = new int[len];
        int max = 0;
        dp[0] = 1;
        for(int i = 1; i < len; i++){
            dp[i] = 1;
            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;
    }
}
  • 贪心 + 二分查找

贪心策略:若我们想要找到的递增子序列尽可能长,那么我们需要让每次递增的幅度尽可能小。

因此使用dp[i]表示长度为i的最长递增子序列末尾元素最小值。使用len记录目前最长递增子序列的长度。

接下来证明dp数组的单调性,反证法:假设有dp[j] > dp[i]j < i。将长度为i的最长递增子序列的后i-j个元素删去,我们可以得到一个长度为j的最长递增子序列,则必有该子序列的第j个元素小于dp[i],根据假设,也小于dp[j]。这样我们就找到了一个长度为j的最长递增子序列,且其末尾元素比dp[j]小,这与我们定义的dp数组含义相矛盾,所以dp数组应该是单调递增的。最后得到的dp数组其实就是所求的最长递增子序列。

算法流程:我们需要从前往后的遍历数组nums,假设当前元素为nums[i],当前的最长递增子序列长度为len。则有两种情况

  • nums[i] > dp[len],则直接将nums[i]添加到dp数组的末尾,同时len的长度加1.
  • nums[i] <= dp[len]从后往前遍历当前dp数组,找到第一个小于nums[i]的元素dp[j],并更新dp[j+1] = nums[i]。由于dp数组是单调的,所以可以使用二分查找来优化时间复杂度。
class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums.length == 0 || nums.length == 1) return nums.length;

        int len = 1;
        int[] dp = new int[nums.length + 1];
        dp[1] = nums[0];
        for(int i = 1; i < nums.length; i++){
            if(nums[i] > dp[len]){
                dp[++len] = nums[i];
            } else {
                int l = 1, r = len, pos = 0;//若所有元素都大于nums[i],则更新dp数组的第一个元素
                while(l <= r){
                    int mid = l + (int) (r - l) / 2;
                    if(dp[mid] < nums[i]){
                        pos = mid;
                        l = mid + 1;
                    } else{
                        r = mid - 1;
                    }
                }
                dp[pos + 1] = nums[i];
            }
        }
        return len;
    }
}

另外,我觉得二分查找需要注意的两个细节

  • while循环结束的条件,我们假设搜索区间是一个长度为len的数组
    • l <= r,表示整个搜索区间是一个闭区间[l , r],此时初始的右边界应该为len - 1,是数组最后一个元素的索引
    • l < r,表示整个搜索区间是一个左闭右开区间[l , r),此时初始的右边界应该是len, 即索引为len是越界的。
  • 每次搜索区间的边界变化
    • 因为这道题目里面,每次搜索前,已经判断过mid位置对应的元素了,下一次搜索时,应该将mid从搜索区间去掉。所以这里是l = mid + 1r = mid - 1

参考官方题解总结的思路,若哪里理解有误,望指正。欢迎讨论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值