各种子序列:

  • 最长上升子序列(LIS):Longest Increasing Subsequence

  • 最长连续序列(LCS):Longest Consecutive Sequence

  • 最长连续递增序列(LCIS):Longest Continuous Increasing Subsequence

  • 最长公共子序列(LCS):Longest Common Subsequence


leetcode题库:

  • 300     动态规划解最长子序列子串等一类问题之最长上升子序列[Thorold's Deer]

  • 673/674动态规划解最长子序列子串等一类问题之最长连续递增序列[Reindeer]

  • 1312动态规划解最长子序列子串等一类问题之让字符串成为回文及其Follow Up[Sika Deer]

  • 128/LC397动态规划解最长子序列子串等一类问题之最长连续子序列[White-lipped Deer]

  • 1143动态规划解最长子序列子串等一类问题之最长公共子序列[Hog Deer]



  1. 动态规划解最长子序列子串等一类问题之最长上升子序列[Thorold's Deer]

子序列篇_第1张图片

子串与子序列区别:子串不可跳跃,子序列可以跳跃,如 “AC”是“ABCDEFG”的子序列,而不是子串。 而“ABC”则是其子串


定义状态:

dp[i]dp[i]表示在区间nums[0....i]区间范围内的最长上升子序列的长度

2020-05-18_082043.png

比较索引ii与其前面出现的某个位置jj的大小

当nums[i]<=nums[j],说明jj处的值要比i处的值的,要是形成子序列则是nums[0...j,i]这样的结果,这时候j到i不能形成递增,以i结尾的子序列所形成的最长子序列的长度等价于以j结尾的子序列所形成的最长子序列的长度,即dp[i]=dp[j]

当nums[i]>nums[j],说明jj处的值小于i处值,形成的子序列是nums[0...j,i]这样的结果,这时候从j到i是递增的,这时候需要在长度上+1,即dp[i]=dp[j+1]

取上述两种情况的max,动态转移方程为: dp[i] = Math.max(dp[i], dp[j] + 1)|0<=j

举例:如果遍历到ii位置,在[0-i] 区间内有[0-j] j

边界条件:

当nums[0...i]只有一个元素时,即以这一个元素结尾的子序列,最长上升子序列是其自身,为1

核心代码

public int lengthOfLIS(int[] nums) {
        //dp[i]: 到i为止 (对于所有 j in [0, i], 记录max length of increasing subsequence
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int len = nums.length;
        int[] dp = new int[len];
        int res = 0;
        for (int i = 0; i < len; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                //i 位置的数与[0,i]位置之间的数比较,如果大于进逻辑
                if (nums[i] > nums[j]) {
                    //等于dp[i]或者dp[j] + 1(j对应的值比i小)的最大值
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }


方法2:贪心+二分

准备一个辅助数组tails,其中tails[i]表示长度为i+1即nums[0...i]的序列尾部元素的值

辅助数组tails一定是严格单调递增的,即在nums[0...j..i]区间上,tails[j]

假设nums[0...j..i]这个区间上,tails[j]>=tails[i],从i这个子序列向前删除i-j个元素,这时候长度变为j的子序列,这时候的尾部元素的值为x

根据tails数组的定义,x

而x又是tails[j]的值,即x=tails[j]

得出tails[i]>tails[j],这与一开始假设的条件矛盾,假设条件不成立

public int lengthOfLISII(int[] nums) {
        int n = nums.length;
        int[] tails = new int[n];
        int end = 0;
        for (int i = 0; i < n; i++) {
            int l = 0, r = end;
            while (l < r) {
                int m = (l + r) / 2;
                if (tails[m] < nums[i]) l = m + 1;
                else r = m;
            }
            tails[l] = nums[i];
            if (end == r) end++;
        }
        return end;
    }



  1. 动态规划解最长子序列子串等一类问题之最长连续递增序列[Reindeer]


子序列篇_第2张图片

题目要求最长连续递增序列,序列本身可以跳跃,但是题意要求连续,也即是不可跳跃

定义状态:

dp[i]表示以i位置结尾,即nums[i]值结尾的,最长连续递增序列的长度


只要关注当前位置i与其前一个的位置i-1的值的大小:

nums[i]>nums[i-1],i至少可以与i-1形成一个连续递增序列,因为它们俩挨着,并且是递增的,长度上是dp[i-1]+1

nums[i]<=nums[i-1],这时候,不能形成连续递增序列,后一个数要比前一个数小,呈下降的趋势,注意=不认为是递增的


方法1:DP(O(N))

public int findLengthOfLCIS(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);//至少可以与其自身形成递增序列
        int max = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] > nums[i - 1]) {
                dp[i] = dp[i - 1] + 1;
                max = Math.max(max, dp[i]);
            }
        }
        return max;
    }


方法2:DP(O(1))

因为只会依赖于i与i-1这两个状态,借助一个常数的级别的一维数组dp[2],辅以奇偶数的小技巧


public int findLengthOfLCIS(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int n = nums.length;
        int max = 1;
        int[] dp = new int[2];
        dp[0] = 1;
        for (int i = 1; i < n; i++) {
            dp[i % 2] = 1;//前一个状态值都会被覆盖,需要重新初始化
            if (nums[i] > nums[i - 1]) {
                dp[i % 2] += dp[(i - 1) % 2];//当前状态依赖前一状态,需要再前一状态上累加
            }
            max = Math.max(max, dp[i % 2]);
        }
        return max;
    }


子序列篇_第3张图片

定义状态:

dp[i]表示以i位置结尾,即nums[i]值结尾的,最长连续递增序列的长度


dp初始化为1,因为nums[i]自身可以形成一个长度为1的最长递增序列


遍历[0...i],再套一层[0...j],其中j<i


当nums[j]<nums[i],说明以[...j,i]这段可以形成最长递增序列,长度是dp[j]+1,其中dp[j]是以j为结尾的最长递增序列的长度

当nums[j]≥nums[i],以[...j,i]是不能形成最长递增序列的,为dp[i],其被初始化为1了

接下来要统计最长递增序列的个数,准备长度n的数组counter,定义count[i]为以nums[i]结尾的最长递增子序列的组合数量,这其中有两个限定条件,一是以nums[i]结尾,二是最长递增子序列,不是最长的不是这个counter数组考虑的,举例,1,2,4,3,5,4,7,2,最长递增序列有1,2,4,5,7;1,2,3,5,7;1,2,3,4,7三种情况,以nums[6]=7结尾的counter[6]=3


下面是如何生成这个counter数组:


总体要满足nums[i] > nums[j],才有意义,这样可以形成递增序列

当dp[j] + 1>dp[i],说明第一次找到以nums[i]为结尾的最长递增子序列,长度为dp[j] + 1,进而可以推出counter[i] = counter[j], 以nums[i]结尾的最长递增子序列的组合数=以nums[j]结尾的最长递增子序列的组合数,这个可以这么理解:当[...j]形成的组合数是值的话,其每一种组合结尾补上[i],即[...j,i],对于组合数本身是没有增加的,还是A值,唯独只是递增子序列的长度+1了

当dp[j] + 1=dp[i],说明这个长度已经找到过一次了,counter[i] += counter[j],现在的组合方式+counter[j]的组合方式

子序列篇_第4张图片

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

复杂度分析

时间复杂度:O(N^2),两个for loop

空间复杂度:O(N),dp与counter数组长度N


  1. 动态规划解最长子序列子串等一类问题之让字符串成为回文及其Follow Up[Sika Deer]


子序列篇_第5张图片

1.Step1

源问题,如上图,让字符串成为回文串的最少插入次数


定义状态

n为str的长度,dp[n][n]是一张二维表

定义:dp[i][j]表示的是子串str[i][j]最少添加多少字符可以使其整体变成一个回文,注意,是最少

要求的结果是dp[0][n-1]表示的是str[0...n-1]也就是这个字符从开头到结尾,要想形成回文,最少的添加字符的个数


如何填写这张dp table呢?

分三大类讨论

1. str[i...j]只有一个字符,即i==j,如图,str[i...j]=A,一个字符要形成回文,很简单,不要向其增加字符,即其本身可以形成回文,即dp[i][j]=0

2. str[i...j]有两个字符

2.1,当str[i]==str[j],即两个字符相同时,其与场景1的情况一样,例如,AA不需要添加字符,即可使其成为回文,即dp[i][j]=0

2.2,当str[i]!=str[j],即两个字符不同时,例如,AB,可以在前面加一个B,变成BAB,也可以在后面加一个A,变成ABA,总之是只要添加一个字符,即可使其变成回文,即dp[i][j]=1

3. str[i...j]有三个或者三个以上字符,这就属于一般的情况了

3.1.当str[i]==str[j]时,例如,ABDCA,只需要考虑BDC,也即要求dp[i][j],转化为求dp[i+1][j-1],只要搞定str[i+1...j-1]部分,使其变成回文,再加上str[i]与str[j],整体就变成回文了,即转移方程:dp[i][j]=dp[i+1][j-1]

3.2.当str[i]!=str[j]时,有两种方法使其变成回文,一种是先让str[i...j-1]变成回文,在ii的左边添加str[j],整体就会变成回文,例如ABECD,可以先考虑ABEC这部分,一个方案是ABECEBA,再在ABECEBA左边加上str[j],即D,带上源字符的D,变成了DABECEBAD;另外一种方案,同理,先让str[i+1...j]变成回文,最后在右边加上str[i],最终变成了整体回文,那么,动态转移方程式什么呢?

要想使str[i...j-1]变成回文,其最少的添加次数是dp[i][j-1],再加上往左侧补的字符,最终应该是dp[i][j-1]+1

同理可得,dp[i+1][j]+1

dp[i][j]=min(dp[i][j-1],dp[i+1][j])+1子序列篇_第6张图片

  • 生成dptable的代码,注意遍历时,综合考虑了以上分析的三种情况

  • 下文中需要使用到这个buildDP,做个一个抽离

public int minInsertions(String s) {
        int[][] dp = buildDP(s.toCharArray());
        return dp[0][s.length() - 1];
    }
public int[][] buildDP(char[] chas) {
        // $dp[i][j]$表示子串$str[i...j]$范围内的最少添加多少个字符后,可以形成回文子串
        int n = chas.length;
        int[][] dp = new int[n][n];
        for (int j = 1; j < n; j++) {
            dp[j - 1][j] = (chas[j - 1] == chas[j]) ? 0 : 1;
            for (int i = j - 2; i >= 0; i--) {
                if (chas[i] == chas[j]) dp[i][j] = dp[i + 1][j - 1];
                else dp[i][j] = Math.min(dp[i + 1][j], dp[i][j - 1]) + 1;
            }
        }
        return dp;
    }

复杂度分析

  • 时间复杂度:O(N^2)N是字符的长度

  • 空间复杂度:O(N^2)dp表的空间


2.Step2

给定一个字符串str,如果可以在str的任意位置添加字符,请返回在添加字符最少的情况下,让str整体都是回文字符串的结果。


这个问题比Step1的问题多了一步,要求返回一种使其变成整体回文的结果,例如,源字符AB,返回一种添加最少字符,使其变成回文的一种方案,返回ABA或者BAB均可


解题思路

准备一个res数组,长度是字符s本身的长度+dp[0][n-1]

运用双指针的想法,给res填充字符

当str[i]==str[j]时,res直接追加即可

当str[i]!=str[j]时,需要找到一个最少添加字符的方案

当dp[i][j - 1] < dp[i + 1][j]时,即最终在生成字符的时候,在左侧填充一个str[j]

当dp[i][j - 1] >= dp[i + 1][j]时,即最终在生成字符的时候,在右侧填充一个str[i]


public String getPalindromeI(String s) {
        char[] chas = s.toCharArray();
        int n = chas.length;
        int[][] dp = buildDP(chas);
        int appendLen = dp[0][n - 1];
        char[] res = new char[n + appendLen];
        int i = 0, j = n - 1;
        int left = 0, right = res.length - 1;
        while (i <= j) {
            if (chas[i] == chas[j]) {
                res[left++] = chas[i++];
                res[right--] = chas[j--];
            } else {
                if (dp[i][j - 1] < dp[i + 1][j]) {
                    res[left++] = chas[j];
                    res[right--] = chas[j--];
                } else if (dp[i][j - 1] >= dp[i + 1][j]) {
                    res[left++] = chas[i];
                    res[right--] = chas[i++];
                }
            }
        }
        return String.valueOf(res);
    }

复杂度分析

时间复杂度:O(N^2),N是字符的长度,求res的过程是O(N),总体是O(N^2)

空间复杂度:O(N^2),dp表的空间


3.Step3

给定一个字符串str,再给定str的最长回文子序列字符串strlps,请返回在添加字符最少的情况下,让str整体都是回文字符串的一种结果。进阶问题比原问题多了一个参数,请做到时间复杂度比原问题的实现低

举例:

str="A1B21C",strlps="121",返回"AC1B2B1CA"或者"CA1B2B1AC",总之,只要是添加的字符数最少,只返回其中一种结果即可


题目分析

这个问题是给出了一个seed字符,生成最少添加字符形成回文后的其中一种结果

解题思路

运用剥洋葱的方式去解决

str的长度为nn,strlps的长度为m,形成回文res的结果是2*n-m ,因为m部分已经是回文了,不需要额外添加

以str=A1BC22DE1F,strlps=1221为例

洋葱的第0层是由strlps[0]与strlps[m-1],组成,即1...1,从str的最左侧开始找,一直找到strlps[0]=1的字符为止,记为leftPart,为A,接着从str的最右侧开始找,一直找到strlps[m-1]=1的字符为止,记为rightPart,为F,

将leftPart+rightPart的逆序leftPart+rightPart的逆序,复制到res的左侧

将rightPart+leftPart的逆序rightPart+leftPart的逆序,复制到res的右侧

结果变成了AF......FA

在加上strlps,变成了AF1......1FA

洋葱进入第1层,即2...2,接着第一层的位置找str,leftPart为BC,rightPart为DE,

将leftPart+rightPart的逆序leftPart+rightPart的逆序,复制到res的左侧

将rightPart+leftPart的逆序rightPart+leftPart的逆序,复制到res的右侧

结果变成了BCED......DECB

在加上strlpsstrlps,变成了BCED2......2DECB

加上上一层,变成了AF1BCED22DECB1FA

洋葱进入第n层。。。

代码部分注释掉了一部分,将resLeft = 0, resRight = 0;定义为全局变量,好理解些

整体实现逻辑是双指针,游走,不过左右指针有很多对,嗯,只能是,细节是魔鬼啊

  //res array 的左右指针,定义为全局变量
    int resLeft = 0, resRight = 0;
    public String getPalindromeII(String str, String strlps) {
        char[] chas = str.toCharArray();//str 目标字符串
        char[] lps = strlps.toCharArray();//base 字符串
        int n = chas.length, m = lps.length;//len
        char[] res = new char[2 * n - m];//res字符串 arr
        int chasLeft = 0, chasRight = n - 1;//str 的左右指针
        int lpsLeft = 0, lpsRight = m - 1;//base 的左右指针
        resRight = res.length - 1;
        int tmpLeft = 0, tmpRight = 0;//tmp 左右指针
        while (lpsLeft <= lpsRight) {//循环遍历lps字符
            tmpLeft = chasLeft;
            tmpRight = chasRight;
            while (chas[chasLeft] != lps[lpsLeft]) chasLeft++;//如果lpsLeft在chas未出现,一直chasLeft++
            while (chas[chasRight] != lps[lpsRight]) chasRight--;//如果lpsRight在chas未出现,一直chasRight--
            build(res, chas, chasLeft, chasRight, tmpLeft, tmpRight);//组装
//            int len = chasLeft - tmpLeft + tmpRight - chasRight;
//            resLeft += len;
//            resRight -= len;
            res[resLeft++] = chas[chasLeft++];//添加当前的lpsLeft ==lpsRight对于的字符串
            res[resRight--] = chas[chasRight--];//添加当前的lpsLeft ==lpsRight对于的字符串
            lpsLeft++;//进入下一轮
            lpsRight--;
        }
        return String.valueOf(res);
    }
    /**
     * @param res       结果arr
     *                  //     * @param resLeft
     *                  //     * @param resRight
     * @param chas      str arr
     * @param chasLeft  str arr left pointer
     * @param chasRight str arr right pointer
     * @param tmpLeft   tmp left pointer
     * @param tmpRight  tmp right pointer
     */
    private void build(char[] res,
                       char[] chas, int chasLeft, int chasRight,
                       int tmpLeft, int tmpRight) {
        for (int i = tmpLeft; i < chasLeft; i++) {
            res[resLeft++] = chas[i];
            res[resRight--] = chas[i];
        }
        for (int j = tmpRight; j > chasRight; j--) {
            res[resLeft++] = chas[j];
            res[resRight--] = chas[j];
        }
    }


  1. 动态规划解最长子序列子串等一类问题之最长连续子序列[White-lipped Deer]

子序列篇_第7张图片

方法1:Sort+Compare

先排序,题意要求连续序列,即可以比较nums[i]与 nums[i - 1],如果不相等,表示是递增的趋势,相等则反之,递增后需要判断是否连续,即相邻的元素差值是否为1


下面的代码处理边界case 如[-1,0],不会比较max与cur的值,需要在最后一道防线拦截一次

Math.max(max,cur);



public int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        Arrays.sort(nums);
        int n = nums.length;
        int max = 1, cur = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] != nums[i - 1]) {
                if (nums[i - 1] + 1 == nums[i]) cur++;
                else {
                    max = Math.max(max, cur);
                    cur = 1;
                }
            }
        }
        return Math.max(max, cur);
    }

复杂度分析

  • 时间复杂度:O(Nlog(N))N是数组的长度,排序的复杂度

  • 空间复杂度:O(1),常量级别的空间

方法2:Hash

准备一个set,首先将所有的num装进set

for loop 数组,如果当前遍历到的元素num-1不在set中,说明这是一段新的可能出现的递增序列,变量curNum置为num,while循环判断curNum+1是否在set中,是则表示是连续的

记录max值

public int longestConsecutive(int[] nums) {
        Set set = new HashSet<>();
        for (int num : nums) set.add(num);
        int max = 0;
        for (int num : nums) {
            if (!set.contains(num - 1)) {//判断set不包含当前元素-1的值,跳过已经计算的最长递增序列
                int curNum = num;
                int curCnt = 1;
                while (set.contains(curNum + 1)) {
                    curNum += 1;
                    curCnt += 1;
                }
                max = Math.max(max,curCnt);
            }
        }
        return max;
    }


复杂度分析

时间复杂度:O(N),尽管在 for 循环中嵌套了一个 while循环,时间复杂度看起来像是二次方级别的。但其实它是线性的算法。因为只有当 curNum 遇到了一个序列的开始,$ while 循环才会被执行(也就是$ curNum-1 不在数组 nums 里), while循环在整个运行过程中只会被迭代N次。这意味着尽管看起来时间复杂度为 O(N^2), 实际这个嵌套循环只会运行 O(N+N)=O(N)次。所有的计算都是线性时间的,所以总的时间复杂度是O(N)的

空间复杂度:O(1),常量级别的空间


最长上升连续子序列

子序列篇_第8张图片

使用 O(n) 时间和 O(1) 额外空间来解决


O(1)的空间复杂度来处理,会比较麻烦,需要压缩空间,题意中的最长上升连续子序列,定义为从左到右和从右到左均可以

准备两个数组,大小都为2:

start记录从左到右的连续递增子序列的最长长度

end记录从右到做的连续递增子序列的最长长度


public int longestIncreasingContinuousSubsequence(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int n = nums.length;
        //从左到右,start
        int[] start = new int[2];
        Arrays.fill(start, 1);
        int maxStart = 1;
        for (int i = 1; i < n; i++) {
            start[i % 2] = 1;
            if (nums[i] > nums[i - 1]) {
                start[i % 2] += start[(i - 1) % 2];
            }
            maxStart = Math.max(maxStart, start[i % 2]);
        }
        //从右到做,end
        int[] end = new int[2];
        Arrays.fill(end, 1);
        int maxEnd = 1;
        for (int i = n - 2; i >= 0; i--) {
            end[i % 2] = 1;
            if (nums[i] > nums[i + 1]) {
                end[i % 2] += end[(i + 1) % 2];
            }
            maxEnd = Math.max(maxEnd, end[i % 2]);
        }
        return Math.max(maxStart, maxEnd);
    }



  1. 动态规划解最长子序列子串等一类问题之最长公共子序列[Hog Deer]

子序列篇_第9张图片

方法1:DP 基础版

子序列篇_第10张图片

对于0位置未添加空字符串

dp[i][j]表示的是s1[0...i-1]与s2[0...j-1]的最长公共子序列的长度,要求的是dp[m-1][n-1]

当s1[i]==s2[j],说明这两个字符是公共的字符,只要考察其子问题,dp[i-1][j-1]的长度即可,在此基础上+1,

当s1[i]!=s2[j],说明这两个字符不是公共的字符,只要考察其两个子问题,dp[i-1][j],dp[i][j-1],取max


动态转移方程:

image.png

 public int longestCommonSubsequence(String text1, String text2) {
        if (text1 == null || text2 == null || text1.length() == 0 || text2.length() == 0) return 0;
        char[] chas1 = text1.toCharArray();
        char[] chas2 = text2.toCharArray();
        int m = chas1.length, n = chas2.length;
        int[][] dp = new int[m][n];
        dp[0][0] = chas1[0] == chas2[0] ? 1 : 0;
        for (int i = 1; i < m; i++) dp[i][0] = chas1[i] == chas2[0] ? 1 : dp[i - 1][0];
        for (int j = 1; j < n; j++) dp[0][j] = chas1[0] == chas2[j] ? 1 : dp[0][j - 1];
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                if (chas1[i] == chas2[j]) dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1);
            }
        }
        return dp[m - 1][n - 1];
    }

方法2:DP 优化版

子序列篇_第11张图片


对于0位置添加空字符串


dp[i][j]表示的是s1[0...i]与s2[0...j]的最长公共子序列的长度,要求的是dp[m][n]

注意s1|s2的位置是错位了一个,其长度达不到m|n


public int longestCommonSubsequence2nd(String text1, String text2) {
        if (text1 == null || text2 == null || text1.length() == 0 || text2.length() == 0) return 0;
        int m = text1.length(), n = text2.length();
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
            }
        }
        return dp[m][n];
    }


方法3:DP压缩空间版O(1)

  • 准备一个dp,n+1的长度,其实的0位置存放一个空字符串

子序列篇_第12张图片

8HD96U.jpg

准备几个变量:

last:表示是当前dp[j](dp[i][j])左上角的数,相当于dp[i-1][j-1],初始化的时候为0

tmp:表示是当前dp[j](dp[i][j])正上方的数,相当于dp[i- 1][j]

dp[j-1]:表示是当前dp[j](dp[i][j])左边的数,相当于dp[i][j-1]

每一轮结束后,last的值都向前滚动一个,变成正上方的数,也就是tmp


public int longestCommonSubsequence3rd(String text1, String text2) {
    if (text1 == null || text2 == null ||
        text1.length() == 0 || text2.length() == 0) return 0;
        int m = text1.length(), n = text2.length();
        int[] dp = new int[n + 1];
        int tmp = 0;
        for (int i = 1; i <= m; i++) {
            int last = 0;
            for (int j = 1; j <= n; j++) {
                tmp = dp[j];
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) dp[j] = last + 1;
                else dp[j] = Math.max(tmp, dp[j - 1]);
                last = tmp;
            }
//            System.out.println(JSON.toJSONString(dp));
        }
        return dp[n];
    }




参考链接:

https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/dong-tai-gui-hua-jie-zui-chang-zi-xu-lie-zi-chua-3/

https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/solution/dong-tai-gui-hua-jie-zui-chang-zi-xu-lie-zi-chua-4/

https://leetcode-cn.com/problems/minimum-insertion-steps-to-make-a-string-palindrome/solution/dong-tai-gui-hua-jie-zui-chang-zi-xu-lie-zi-chuan-/

https://leetcode-cn.com/problems/minimum-insertion-steps-to-make-a-string-palindrome/solution/dong-tai-gui-hua-jie-zui-chang-zi-xu-lie-zi-chuan-/

https://leetcode-cn.com/problems/longest-consecutive-sequence/solution/dong-tai-gui-hua-jie-zui-chang-zi-xu-lie-zi-chua-5/

https://leetcode-cn.com/problems/longest-common-subsequence/solution/a-fei-xue-suan-fa-zhi-by-a-fei-8/