• leetcode 300. 最长上升子序列


    题目描述

    给定一个无序的整数数组,找到其中最长上升子序列的长度。
    
    示例:
    
    输入: [10,9,2,5,3,7,101,18]
    输出: 4 
    解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
    说明:
    
    可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
    你算法的时间复杂度应该为 O(n2) 。
    进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
    
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    代码

    代码1. 动态规划

    解题思路:
    
    状态定义:
    
      dp[i] 代表 nums 前 i 个数字的最长子序列长度。
    
    转移方程:
    
      设 j∈[0,i),考虑每轮计算新 dp[i] 时,遍历 i 之前的 dp 列表区间(即 [0,i) 区间);
      当 nums[i] > nums[j] 时: nums[i]可以接在 nums[j] 之后(此题要求严格递增),此情况下最长上升子序列长度为 dp[j] + 1 ;
      当 nums[i] <= nums[j]时: nums[i]无法接在 nums[j]之后,此情况上升子序列不成立,跳过。
      上述所有情况下计算出的 dp[j] + 1 的最大值,为直到 i 的最长上升子序列长度,实现方式为遍历 j 时每轮执行 dp[i] = max(dp[i], dp[j] + 1)

     初始状态:令 dp 列表所有值 =1,含义为每个数字单独组成序列时,长度都为 1 。

     返回值:返回 dpdp 列表的最大值,即最终最长上升子序列。

    
    
    复杂度分析:
      时间复杂度 O(N^2): 遍历计算 dp 列表需 O(N) ,计算每个 dp[i] 需 O(N) 。
      空间复杂度 O(N)O(N) : dpdp 列表占用线性大小额外空间。

    粗暴总结:
      遍历dp, 找 i 前面的所有小于 nums[i] 的数字的 max(dp)+1

    代码

    // Dynamic programming.
    public class Solution {
        public int lengthOfLIS(int[] nums) {
            int len = nums.length;
            if (len == 0) {
                return 0;
            }
            int[] dp = new int[len]; //dp[i] 代表 nums 前 i个数字的最长子序列长度。
            dp[0] = 1;
            int max = 1; //保存最长上升序列长度
            for (int i = 1; i < len; i++) {
                int maxj = 0; //保存 i 前面的所有小于 nums[i] 的数字中的最大dp[j]值;
                for (int j = 0; j < i; j++) {
                    if (nums[i] > nums[j]) {
                        maxj = Math.max(maxj, dp[j]);
                    }
                }
                dp[i] = maxj + 1;
                max = Math.max(max, dp[i]);
            }
            return max;
        }
    }

    精简版本

    //Dynamic programming.
    class Solution {
     public int lengthOfLIS(int[] nums) {
         int len = nums.length;
         if (len == 0) {
             return 0;
         }
         int[] dp = new int[len];
         int max = 0;
         Arrays.fill(dp, 1);//令 dp列表所有值 =1, 含义为每个数字单独组成序列时,长度都为 1 。
         for(int i = 0; i < len; i++) {
             for(int j = 0; j < i; j++) {
                 if(nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
             }
             max = Math.max(max, dp[i]);
         }
         return max;
     }
    }

    代码2. 动态规划+二分查找

    解题思路:
    
    降低复杂度切入点: 
        遍历计算 dp 列表需 O(N) ,计算每个 dp[i] 需 O(N) 。
        动态规划中,通过线性遍历来计算 dp 的复杂度无法降低;
        每轮计算中,需要通过线性遍历 [0,i) 区间元素来得到 dp[i]。
    
    改进思路:
        设常量数字 N ,和随机数字 x ,我们可以容易推出:当 N 越小时,N<x 的几率越大。
        重新设计状态定义,使整个 dp 为一个排序列表;
        这样在计算每个 dp[i] 时,就可以通过二分法遍历 [0,i) 区间元素,将此部分复杂度由 O(N)降至 O(logN) 。
    
    
    状态定义:
    
        dp[i] 的值代表 子序列的长度 为 i 时,此序列尾部元素的值。
    
    转移方程:
        
      遍历计算每个 dp[i],不断更新长度为 [
    1,i] 的子序列尾部元素值,始终保持每个尾部元素值最小 初始状态:
      令 dp 列表所有值
    =1 ,含义为每个数字单独组成序列时,长度都为 1 。 返回值:
      返回 dp 列表的最大值,即最终最长上升子序列。

    代码

    //Dynamic programming + Dichotomy.
    class Solution {
     public int lengthOfLIS(int[] nums) {
         int[] dp = new int[nums.length];//dp[i] 的值代表 子序列的长度 为 i 时,此序列尾部元素的值。
         int max = 0;// 最长子序列的长度
         for(int num : nums) {
             int i = 0, j = max;
             while(i < j) {
                 int m = (i + j) / 2;
                 if(dp[m] < num) i = m + 1;
                 else j = m;
             }
             dp[i] = num; //二分法找到大于num的第一个值覆盖
             max = Math.max(max, i + 1);//更新最大长度
         }
         return max;
     }
    }
  • 相关阅读:
    在qt中用tcp传输xml消息
    Response.Redirect 打开新窗体的两种方法
    div:给div加滚动栏 div的滚动栏设置
    高速排序算法
    海量数据处理面试题集锦
    VB中DateDiff 函数解释
    FusionCharts简单教程(一)---建立第一个FusionCharts图形
    mysql 加入列,改动列,删除列。
    Lucene教程具体解释
    Windows7下的免费虚拟机(微软官方虚拟机)
  • 原文地址:https://www.cnblogs.com/haimishasha/p/11460888.html
Copyright © 2020-2023  润新知