【Leetcode】DP | 序列及子数组问题

300 最长递增子序列

求数组最长严格递增子序列的长度。

D [ i ] D[i] D[i]代表以 n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列的长度。
D [ i ] = max ⁡ j < i ,   n u m s [ i ] > n u m s [ j ] ( D [ j ] + 1 ) D[i] = \max_{j < i,\ nums[i]>nums[j]}(D[j] + 1) D[i]=j<i, nums[i]>nums[j]max(D[j]+1)
Q1: O ( n 2 ) O(n^2) O(n2)版本如何提速到 O ( n log ⁡ n ) O(n\log n) O(nlogn)

Q2:能否输出路径?这个不难,增加一个 r e c o r d record record​数组就好。


写了第二遍了,但完全找不到贪心+二分查找的记忆……但看了一遍又不得不感叹巧妙。

贪心思想:保持长度为 i i i的递增子序列,尾部元素值最小。比如[1,2,3,5][1,2,3,4],优先选择 [ 1 , 2 , 3 , 4 ] [1,2,3,4] [1,2,3,4]

维护一个列表 t a i l tail tail t a i l [ i ] tail[i] tail[i]表示长度为 i + 1 i+1 i+1的递增子序列,能够获得的最小尾部元素值。

  • n u m s [ i ] nums[i] nums[i]大于 t a i l tail tail数组的最后一个有效元素时,将其放在 t a i l tail tail数组的最后;
  • 否则插入 n u m s [ i ] nums[i] nums[i],若 t a i l s [ j − 1 ] < n u m s [ i ] ≤ t a i l s [ j ] tails[j-1] < nums[i] \le tails[j] tails[j1]<nums[i]tails[j],则插入 n u m s [ i ] nums[i] nums[i]到位置 j j j

注意 t a i l tail tail数组中的元素并不一定是路径,反例为下图中蓝色部分。

【Leetcode】DP | 序列及子数组问题_第1张图片

674 最长连续递增子序列

D [ i ] = n u m s [ i ] > n u m s [ i − 1 ]   ?   D [ i − 1 ] + 1 : 0 D[i] = nums[i]>nums[i-1]\ ?\ D[i-1] + 1 : 0 D[i]=nums[i]>nums[i1] ? D[i1]+1:0

class Solution {
    public int findLengthOfLCIS(int[] nums) {
        int maxLength = 1;
        int length = 1;
        for (int i = 1; i < nums.length; i++) {
            length = nums[i] > nums[i - 1] ? length + 1 : 1;
            maxLength = Math.max(length, maxLength);
        }
        return maxLength;
    }
}

718 最长重复子数组

给定两个数组,返回两个数组中公共的、长度最长的子数组。

D [ i , j ] D[i,j] D[i,j]表示以 n u m s 1 [ i ] 、 n u m s 2 [ j ] nums1[i]、nums2[j] nums1[i]nums2[j]结尾的公共子数组最大长度。

TODO:滑动窗口解法。

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        int[][] D = new int[m][n];
        int max = Integer.MIN_VALUE;

        for (int i = 0; i < m; i++) {
            D[i][0] = nums1[i] == nums2[0] ? 1 : 0;
            max = Math.max(D[i][0], max);
        }

        for (int j = 0; j < n; j++) {
            D[0][j] = nums1[0] == nums2[j] ? 1 : 0;
            max = Math.max(D[0][j], max);
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                D[i][j] = nums1[i] == nums2[j] ? D[i - 1][j - 1] + 1 : 0;
                max = Math.max(D[i][j], max);
            }
        }

        return max;
    }
}

1143 最长公共子序列

D [ i , j ] D[i,j] D[i,j]表示 n u m s 1 [ 0 : i ] 、 n u m s [ 0 : j ] nums1[0:i]、nums[0:j] nums1[0:i]nums[0:j]的公共子序列最大长度。

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        char[] str1 = text1.toCharArray();
        char[] str2 = text2.toCharArray();
        
        int m = str1.length;
        int n = str2.length;
        int[][] D = new int[m][n];

        D[0][0] = str1[0] == str2[0] ? 1 : 0;
        for (int i = 1; i < m; i++) {
            D[i][0] = D[i - 1][0] == 1 || str1[i] == str2[0] ? 1 : 0;
        }

        for (int j = 1; j < n; j++) {
            D[0][j] = D[0][j - 1] == 1 || str1[0] == str2[j] ? 1 : 0;
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                D[i][j] = str1[i] == str2[j] ? D[i - 1][j - 1] + 1 : Math.max(D[i - 1][j], D[i][j - 1]);
            }
        }

        return D[m - 1][n -1];
    }
}

1035 不相交的线

感觉和上一题是一样滴耶。

D [ i , j ] D[i,j] D[i,j]表示 n u m s 1 [ 0 : i ] 、 n u m s [ 0 : j ] nums1[0:i]、nums[0:j] nums1[0:i]nums[0:j]最多不相交的线的数量。
【Leetcode】DP | 序列及子数组问题_第2张图片

class Solution {
    public int maxUncrossedLines(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;

        int[][] D = new int[m][n];
        D[0][0] = nums1[0] == nums2[0] ? 1 : 0;
        for (int i = 1; i < m; i++) {
            D[i][0] = D[i - 1][0] == 1 || nums1[i] == nums2[0] ? 1 : 0;
        }
        for (int j = 1; j < n; j++) {
            D[0][j] = D[0][j - 1] == 1 || nums1[0] == nums2[j] ? 1 : 0;
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                D[i][j] = nums1[i] == nums2[j] ? D[i-1][j-1] + 1 : Math.max(D[i][j - 1], D[i - 1][j]);
            }
        }

        return D[m - 1][n - 1];
    }
}

392 判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

相当于只使用删除操作的编辑距离问题。

和之前问题的不同:只对其中一个串进行操作,利用另一个串的信息(而不进行操作)

D [ i , j ] D[i,j] D[i,j]表示字符串 s [ 0 : i ] s[0:i] s[0:i]和字符串 t [ 0 : j ] t[0:j] t[0:j] s [ i ] s[i] s[i]结尾的最大公共子序列长度
D [ i , j ] = { D [ i − 1 ] [ j − 1 ] + 1 , if  s [ i ] = s [ j ] D [ i ] [ j − 1 ] , else  D[i,j] = \begin{cases} D[i-1][j-1] + 1, & \text{if }s[i]=s[j] \\ D[i][j - 1], & \text{else } \end{cases} D[i,j]={D[i1][j1]+1,D[i][j1],if s[i]=s[j]else 
【Leetcode】DP | 序列及子数组问题_第3张图片

class Solution {
    public boolean isSubsequence(String s, String t) {
        if (s.length() == 0) {
            return true;
        }
        if (t.length() == 0) {
            return false;
        }

        char[] src = t.toCharArray();
        char[] target = s.toCharArray();

        int m = target.length;
        int n = src.length;

        // 初始化
        int[][] D = new int[m][n];
        D[0][0] = src[0] == target[0] ? 1 : 0;
        for (int i = 1; i < m; i++) {
            D[i][0] = target[i] == src[0] ? 1 : 0;
        }
        for (int j = 1; j < n; j++) {
            D[0][j] = D[0][j - 1] == 1 || target[0] == src[j] ? 1 : 0;
        }

        // 计算
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                D[i][j] = target[i] == src[j] ? D[i - 1][j - 1] + 1 : D[i][j - 1];
            }
        }

        return D[m - 1][n - 1] == target.length;
    }
}

115 不同的子序列

D [ i , j ] D[i,j] D[i,j]表示字符串 s [ 0 : i ] s[0:i] s[0:i]含有子序列 t [ 0 : j ] t[0:j] t[0:j]的个数。
D [ i , j ] = { D [ i − 1 ] [ j − 1 ] + D [ i − 1 ] [ j ] , if  s [ i ] = s [ j ] D [ i − 1 ] [ j ] , else  D[i,j] = \begin{cases} D[i-1][j-1] + D[i - 1][j], & \text{if }s[i]=s[j] \\ D[i - 1][j], & \text{else } \end{cases} D[i,j]={D[i1][j1]+D[i1][j],D[i1][j],if s[i]=s[j]else 

  • s [ i ] s[i] s[i] t [ j ] t[j] t[j]相等:
    • s [ 0 : i − 1 ] s[0:i-1] s[0:i1]含有子序列 t [ 0 : j − 1 ] t[0:j-1] t[0:j1]的个数 +
    • s [ 0 : i − 1 ] s[0:i-1] s[0:i1]含有子序列 t [ 0 : j ] t[0:j] t[0:j]的个数
  • 不相等:
    • s [ 0 : i − 1 ] s[0:i-1] s[0:i1]含有子序列 t [ 0 : j ] t[0:j] t[0:j]的个数
class Solution {
    public int numDistinct(String s, String t) {
        char[] src = s.toCharArray();
        char[] target = t.toCharArray();

        int m = src.length;
        int n = target.length;
        int[][] D = new int[m][n];

        D[0][0] = src[0] == target[0] ? 1 : 0;
        for (int i = 1; i < m; i++) {
            D[i][0] = D[i - 1][0] + (src[i] == target[0] ? 1 : 0);
        }
        // 其实这一段不用初始化的,默认就是0,但还是写一下明确一下
        for (int j = 1; j < n; j++) {
            D[0][j] = 0;
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                D[i][j] = src[i] == target[j] ? D[i - 1][j - 1] + D[i - 1][j] : D[i - 1][j];
            }
        }

        return D[m - 1][n - 1];
    }
}

583 两个字符串的删除操作

D [ i , j ] D[i,j] D[i,j]表示将 w o r d 1 [ 0 : i ] word1[0:i] word1[0:i] w o r d 2 [ 0 : j ] word2[0:j] word2[0:j]变为相同串的最小删除步数。
D [ i , j ] = { D [ i − 1 ] [ j − 1 ] , if  s [ i ] = s [ j ] min ⁡ ( D [ i − 1 ] [ j ] , D [ i ] [ j − 1 ] ) + 1 , else  D[i,j] = \begin{cases} D[i-1][j-1], & \text{if }s[i]=s[j] \\ \min(D[i - 1][j], D[i][j-1]) + 1, & \text{else } \end{cases} D[i,j]={D[i1][j1],min(D[i1][j],D[i][j1])+1,if s[i]=s[j]else 

class Solution {
    public int minDistance(String word1, String word2) {
        char[] str1 = word1.toCharArray();
        char[] str2 = word2.toCharArray();

        int m = str1.length;
        int n = str2.length;
        int[][] D = new int[m][n];

        D[0][0] = str1[0] == str2[0] ? 0 : 2;
        // 如果相等,需要删除i个字符,否则删除D[i - 1][0] + 1个字符
        for (int i = 1; i < m; i++) {
            D[i][0] = str1[i] == str2[0] ? i : D[i - 1][0] + 1;
        }
        for (int j = 1; j < n; j++) {
            D[0][j] = str1[0] == str2[j] ? j : D[0][j - 1] + 1;
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (str1[i] == str2[j]) {
                    D[i][j] = D[i - 1][j - 1];
                }
                else {
                    D[i][j] = Math.min(D[i - 1][j], D[i][j - 1]) + 1;
                }
            }
        }
        
        return D[m - 1][n - 1];
    }
}

72 编辑距离

D [ i , j ] D[i,j] D[i,j]表示将 w o r d 1 [ 0 : i ] word1[0:i] word1[0:i]转换为 w o r d 2 [ 0 : j ] word2[0:j] word2[0:j]的最小操作数。

  • 删除:

D [ i , j ] = { D [ i − 1 ] [ j − 1 ] , if  s [ i ] = s [ j ] D [ i − 1 ] [ j ] + 1 , else  D[i,j] = \begin{cases} D[i-1][j-1], & \text{if }s[i]=s[j] \\ D[i - 1][j]+1, & \text{else } \end{cases} D[i,j]={D[i1][j1],D[i1][j]+1,if s[i]=s[j]else 

  • 插入:

D [ i , j ] = { D [ i − 1 ] [ j − 1 ] , if  s [ i ] = s [ j ] D [ i ] [ j − 1 ] + 1 , else  D[i,j] = \begin{cases} D[i-1][j-1], & \text{if }s[i]=s[j] \\ D[i][j - 1]+1, & \text{else } \end{cases} D[i,j]={D[i1][j1],D[i][j1]+1,if s[i]=s[j]else 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-72pVPAvn-1686504006491)(【Leetcode】DP-编辑距离问题/image-20230612005502550.png)]

  • 替换:

D [ i , j ] = { D [ i − 1 ] [ j − 1 ] , if  s [ i ] = s [ j ] D [ i − 1 ] [ j − 1 ] + 1 , else  D[i,j] = \begin{cases} D[i-1][j-1], & \text{if }s[i]=s[j] \\ D[i - 1][j - 1]+1, & \text{else } \end{cases} D[i,j]={D[i1][j1],D[i1][j1]+1,if s[i]=s[j]else 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0aA9fqVi-1686504006491)(【Leetcode】DP-编辑距离问题/image-20230612005625559.png)]

所以:
D [ i , j ] = { D [ i − 1 ] [ j − 1 ] , if  s [ i ] = s [ j ] min ⁡ ( D [ i − 1 ] [ j ] , D [ i ] [ j − 1 ] , D [ i − 1 ] [ j ] ) + 1 , else  D[i,j] = \begin{cases} D[i-1][j-1], & \text{if }s[i]=s[j] \\ \min(D[i-1][j],D[i][j-1],D[i - 1][j])+1, & \text{else } \end{cases} D[i,j]={D[i1][j1],min(D[i1][j],D[i][j1],D[i1][j])+1,if s[i]=s[j]else 
两种写法,一种从空串开始,更推荐这种写法,不易出错:

class Solution {
    public int minDistance(String word1, String word2) {
        char[] str1 = word1.toCharArray();
        char[] str2 = word2.toCharArray();

        int m = str1.length;
        int n = str2.length;
        int[][] D = new int[m + 1][n + 1];

        // 二者都是空串
        D[0][0] = 0;
        // 把str1[0:i-1]变为空串
        for (int i = 1; i <= m; i++) {
            D[i][0] = i;
        }
        // 把空串变为str2[0:j-1]
        for (int j = 1; j <= n; j++) {
            D[0][j] = j;
        }

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                D[i][j] = str1[i - 1] == str2[j - 1] ? D[i - 1][j - 1] : Math.min(Math.min(D[i - 1][j], D[i][j - 1]), D[i - 1][j - 1]) + 1;
            }
        }

        return D[m][n];
    }
}

一种从单个字符开始:

class Solution {
    public int minDistance(String word1, String word2) {
        if (word1.length() == 0) {
            return word2.length();
        }
        if (word2.length() == 0) {
            return word1.length();
        }
        
        char[] str1 = word1.toCharArray();
        char[] str2 = word2.toCharArray();

        int m = str1.length;
        int n = str2.length;
        int[][] D = new int[m][n];

        D[0][0] = str1[0] == str2[0] ? 0 : 1;
        for (int i = 1; i < m; i++) {
            D[i][0] = str1[i] == str2[0] ? i : D[i - 1][0] + 1;
        }
        for (int j = 1; j < n; j++) {
            D[0][j] = str1[0] == str2[j] ? j : D[0][j - 1] + 1;
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                D[i][j] = str1[i] == str2[j] ? D[i - 1][j - 1] : Math.min(Math.min(D[i - 1][j], D[i][j - 1]), D[i - 1][j - 1]) + 1;
            }
        }

        return D[m - 1][n - 1];
    }
}

你可能感兴趣的:(Leetcode题解总结,leetcode,算法)