从零学算法300

文章介绍了如何使用动态规划和贪心算法解决给定整数数组的最长严格递增子序列问题,包括二重循环实现的dp方法以及利用dp数组尾部元素最小值的贪心策略,同时提及了二分查找用于替换dp数组的过程。
摘要由CSDN通过智能技术生成

300.给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1

  • 最容易想到的肯定就是二重循环的 dp,设 dp[i] 为以 nums[i] 结尾的最长子序列的长度,这时就不是简单的根据 dp[i-1] 推导,因为这个子序列是从前面任何一点连接过来的,比如 […,7,6,9],我这个子序列可能是 [ …,7,9] 也可能是 […,6,9],所以我们要取最大的情况
  •   public int lengthOfLIS(int[] nums) {
          int[] dp = new int[nums.length];
          Arrays.fill(dp,1);
          int max = 1;
          for(int i=1;i<nums.length;i++){
          	// 往前面所有点找,如果有小于 i 点的说明可能是从 j 连接到 i 的子序列
              for(int j=i-1;j>=0;j--){
                  if(nums[i]>nums[j])dp[i]=Math.max(dp[j]+1,dp[i]);
              }
              // 取 dp[] 中的最大值,不一定是以 nums 尾部结尾的子序列长度最长
              max = Math.max(dp[i],max);
          }
          return max;
      }
    
  • 还有个 dp + 贪心 + 二分查找的思路:比如两个递增子序列 [1,2,3] 和 [1,2,4],我们会觉得哪个更好,应该是前者,因为它的尾端元素更小,有更大的可能延长下去,所以我们定义 dp[i] 为长度为 i+1 的递增子序列的尾部最小取值,我们初始化 dp[0] 为 nums[0],在遍历过程中只会遇到两种情况
  • nums[i] 大于 dp 尾部元素:那么就把 nums[i] 加在 dp 数组末尾
  • nums[i] 小于 dp 尾部元素:那么就把 nums[i] 替换掉最先大于 dp 数组中的某个数
  • 比如数组 [0,3,1,6,2,2,7,8]的遍历过程:
    1. dp[0] = 0
    2. dp[1] = 3,此时子序列为 03
    3. 这时我们找到了更好的子序列,所以替换 dp[1] 为 1
    4. dp[2] = 6,此时子序列为 016
    5. 我们又找到了更好的子序列,所以替换为 dp[2] 为 2
    6. 跳过
    7. dp[3] = 7
    8. dp[4] = 8
  • 最终我们能得到最长递增子序列 01678
  • 值得注意的是,在遍历过程中,dp 数组并不表示一个完整的递增子序列,这也和我们的定义有关,因为我们只关心递增子序列的尾部元素,所以我们能保证的只是 dp[i] 这一个数是长度为 i+1 的递增子序列的尾部最小取值情况下的尾部元素,而上面的例子只是恰好每次都替换了尾部元素,使得 dp 数组一直能表示一个子序列。
  • 比如数组 [1,2,4,3] 只包含 2 个长度为 3 的子序列 123 和 124,我们只保证 dp[2] 是长度为 3 的最优(尾部元素最小)的递增子序列 123 的尾部元素 3。
  • 比如数组 [3,5,6,2] 我们会得到 dp 数组为 [2,5,6],但并不存在子序列 256,他们只表示长度为 1 的最优子序列为 2,长度为 2 的最优子序列为 x5,长度为 3 的最优子序列为 xx6,管他 x 是几,我们只要得到最后子序列的长度就好
  •   public int lengthOfLIS(int[] nums) {
          int[] dp = new int[nums.length];
          dp[0] = nums[0];
          int index = 0;
          for(int i=1;i<nums.length;i++){
          	// 大于就添加到 dp 数组尾部
              if(nums[i]>dp[index])dp[++index]=nums[i];
              // 小于就替换 dp 数组中最接近且大于它的数
              else binaryReplace(dp,index,nums[i]);
          }
          return index+1;
      }
      // 用二分法把小于 dp 数组尾端元素的 nums[i] 替换到 dp
      public void binaryReplace(int[] dp,int index,int n){
          int left = 0, right = index;
          while(left<=right){
              int mid = left+(right-left)/2;
              if(dp[mid]==n)return;
              else if(dp[mid]<n)left = mid+1;
              else right = mid-1;
          }
          dp[left]=n;
      }
    
  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值