子序列问题

子序列不连续
  • 最长递增子序列(300)

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

    动态规划

    class Solution {
        public int lengthOfLIS(int[] nums) {
            // dp[i]表示以nums[i]结尾的最长递增子序列的长度
            int[] dp = new int[nums.length];
            // 每个元素单独成为子序列
            Arrays.fill(dp, 1);
            int res = 1;
            for (int i = 1; i < nums.length; i++) {
                for (int j = 0; j < i; j++) {
                    if (nums[i] > nums[j]) {
                        dp[i] = Math.max(dp[i], dp[j] + 1); 
                    }
                    res = Math.max(dp[i], res);
                }
            }
            return res;
        }
    }
    

    贪心+二分查找

    public class Solution {
        public int lengthOfLIS(int[] nums) {
            int len = nums.length;
            if (len <= 1) {
                return len;
            }
    
            // tail[i]表示长度为 i + 1 的上升子序列的末尾值
            int[] tail = new int[len];
            // 遍历第1个数,直接放在有序数组 tail 的开头
            tail[0] = nums[0];
            // end 表示有序数组 tail 的最后一个已经赋值元素的索引
            int end = 0;
    
            for (int i = 1; i < len; i++) {
                int left = 0;
                int right = end + 1;
                // 二分法查找当前元素在上升子序列中应该插入的位置
                while (left < right) {
                    int mid = left + (right - left) / 2;
                    if (tail[mid] < nums[i]) {
                        left = mid + 1;
                    } else {
                        right = mid;
                    }
                }
                // 当left = right 时循环结束,当前元素插入该位置
                tail[left] = nums[i];
                // 如果当前元素添加在子序列的末尾,即子序列长度+1,则更新end
                if (left == end + 1) {
                    end++;
                }
            }
    		// 返回子序列长度
            return end + 1;
        }
    }
    
  • 最长公共子序列(1143)

    给定两个字符串 text1text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0

    动态规划

    // 二维dp
    class Solution {
        public int longestCommonSubsequence(String text1, String text2) {
            int m = text1.length();
            int n = text2.length();
            // dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
            int[][] dp = new int[m + 1][n + 1];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    // text1[i] 与 text2[j]相同,则dp[i+1][j+1] = dp[i][j] + 1;
                    if (text1.charAt(i) == text2.charAt(j)) {
                        dp[i + 1][j + 1] = dp[i][j] + 1;
                    // text1[i] 与 text2[j]不同,则看text1[0, i]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j]的最长公共子序列。
                    } else {
                        dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1]);
                    }
                }
            }
            return dp[m][n];
        }
    }
    
    // 一维dp
    class Solution {
        public int longestCommonSubsequence(String text1, String text2) {
            int m = text1.length();
            int n = text2.length();
            // dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
            int[] dp = new int[n + 1];
            // dp[i][j]  <!=>  dp[j]  <=>  dp[i + 1][j]
            for (int i = 0; i < m; i++) {
                // pre相当于dp[i][j]
                int pre = dp[0];
                for (int j = 0; j < n; j++) {
                    int cur = dp[j + 1];
                    if (text1.charAt(i) == text2.charAt(j)) {
                        // 此时的pre = dp[j] = dp[i][j]
                        dp[j + 1] = pre + 1;
                    } else {
                        // dp[j]     相当于   dp[i + 1][j]
                        // dp[j + 1] 相当于   dp[i][j + 1]
                        dp[j + 1] = Math.max(dp[j], dp[j + 1]);
                    }
                    pre = cur;
                }
            }
            return dp[n];
        }
    }
    
  • 不相交的线(1143)

    在两条独立的水平线上按给定的顺序写下 nums1nums2 中的整数,可以绘制一些连接两个数字 nums1[i]nums2[j] 的直线,这些直线需要同时满足满足:

    • nums1[i] == nums2[j]
    • 且绘制的直线不与任何其他连线(非水平线)相交。

    其实就是求最长公共子序列!代码同上

子序列连续
  • 最长连续递增序列(674)

    给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

    动态规划

    class Solution {
        public int findLengthOfLCIS(int[] nums) {
            int res = 1;
            int[] dp = new int[nums.length];
            Arrays.fill(dp, 1);
            for (int i = 1; i <nums.length; i++) {
                if (nums[i] > nums[i - 1]) {
                    dp[i] = dp[i - 1] + 1;
                }
                res = Math.max(res, dp[i]);
            }
            return res;
        }
    }
    

    贪心

    class Solution {
        public int findLengthOfLCIS(int[] nums) {
            int res = 1;
            int count = 1;
            for (int i = 1; i <nums.length; i++) {
                if (nums[i] > nums[i - 1]) {
                    count++;
                } else {
                    count = 1;
                }
                res = Math.max(res, count);
            }
            return res;
        }
    }
    
  • 最长重复子数组(718)

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

    动态规划

    // 二维dp
    class Solution {
        public int findLength(int[] nums1, int[] nums2) {
            int m = nums1.length;
            int n = nums2.length;
            // dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。
            int[][] dp = new int[m + 1][n + 1];
            int res = 0;
            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= n; j++) {
                    if (nums1[i - 1] == nums2[j - 1]) {
                        dp[i][j] = dp[i - 1][j - 1] + 1;
                    }
                    res = Math.max(res, dp[i][j]);
                }
            }
            return res;
        }
    }
    
    // 一维dp
    class Solution {
        public int findLength(int[] nums1, int[] nums2) {
            int m = nums1.length;
            int n = nums2.length;
            int[] dp = new int[n + 1];
            int res = 0;
            for (int i = 1; i <= m; i++) {
                // 倒序遍历,防止覆盖
                for (int j = n; j > 0; j--) {
                    if (nums1[i - 1] == nums2[j - 1]) {
                        dp[j] = dp[j - 1] + 1;
                    } else {
                        // 不同时要赋0
                        dp[j] = 0;
                    }
                    res = Math.max(res, dp[j]);
                }
            }
            return res;
        }
    }
    

    滑动窗口

    class Solution {
        public int findLength(int[] nums1, int[] nums2) {
            int m = nums1.length;
            int n = nums2.length;
            int res = 0;
            for (int i = 0; i < m; i++) {
                int len = Math.min(n, m - i);
                int maxlen = maxLength(nums1, nums2, i, 0, len);
                res = Math.max(res, maxlen);
            }
            for (int i = 0; i < n; i++) {
                int len = Math.min(m, n - i);
                int maxlen = maxLength(nums1, nums2, 0, i, len);
                res = Math.max(res, maxlen);
            }
            return res;
        }
    
        public int maxLength(int[] A, int[] B, int addA, int addB, int len) {
            int ret = 0, k = 0;
            for (int i = 0; i < len; i++) {
                if (A[addA + i] == B[addB + i]) {
                    k++;
                } else {
                    k = 0;
                }
                ret = Math.max(ret, k);
            }
            return ret;
        }
    }
    
  • 最大子数组和(53)

    给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

    动态规划

    class Solution {
        public int maxSubArray(int[] nums) {
            // dp[i]:包括下标i的最大连续子序列和为dp[i]
            int[] dp = new int[nums.length];
            dp[0] = nums[0];
            int res = nums[0];
            for (int i = 1; i < nums.length; i++) {
                dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
                res = Math.max(res, dp[i]);
            }
            return res;
        }
    }
    

    贪心

    class Solution {
        public int maxSubArray(int[] nums) {
            int pre = nums[0];
            int res = nums[0];
            for (int i = 1; i < nums.length; i++) {
                pre = Math.max(pre + nums[i], nums[i]);
                res = Math.max(res, pre);
            }
            return res;
        }
    }
    

    分治

    public class Solution {
        public int maxSubArray(int[] nums) {
            int len = nums.length;
            if (len == 0) {
                return 0;
            }
            return maxSubArraySum(nums, 0, len - 1);
        }
        
        //  查询序列 nums [left, right] 区间内的最大子数组和
        private int maxSubArraySum(int[] nums, int left, int right) {
            if (left == right) {
                return nums[left];
            }
            int mid = left + (right - left) / 2;
            // 最大值在左区间、右区间、跨越左右区间出现
            return max3(maxSubArraySum(nums, left, mid),
                    maxSubArraySum(nums, mid + 1, right),
                    maxCrossingSum(nums, left, mid, right));
        }
        
        private int maxCrossingSum(int[] nums, int left, int mid, int right) {
            // 一定会包含 nums[mid] 这个元素
            int sum = 0;
            int leftSum = Integer.MIN_VALUE;
            // 左半边包含 nums[mid] 元素,最多可以到什么地方
            // 计算以 mid 结尾的最大的子数组的和
            for (int i = mid; i >= left; i--) {
                sum += nums[i];
                leftSum = Math.max(leftSum, sum);
            }
            sum = 0;
            int rightSum = Integer.MIN_VALUE;
            // 右半边不包含 nums[mid] 元素,最多可以到什么地方
            // 计算以 mid+1 开始的最大的子数组的和
            for (int i = mid + 1; i <= right; i++) {
                sum += nums[i];
    			rightSum = Math.max(rightSum, sum);
            }
            return leftSum + rightSum;
        }
    
        private int max3(int num1, int num2, int num3) {
            return Math.max(num1, Math.max(num2, num3));
        }
    }
    
编辑距离
  • 判断子序列(392)

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

    动态规划

    // 二维dp
    class Solution {
        public boolean isSubsequence(String s, String t) {
            int m = s.length();
            int n = t.length();
            // dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]
            int[][] dp = new int[m + 1][n + 1];
            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= n; j++) {
                    if (s.charAt(i - 1) == t.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1] + 1;
                    } else {
                        dp[i][j] = dp[i][j - 1];
                    }
                }
            }
            return dp[m][n] == m ? true : false;
        }
    }
    
    // 一维dp
    class Solution {
        public boolean isSubsequence(String s, String t) {
            int m = s.length();
            int n = t.length();
            // dp[j] 表示字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度
            int[] dp = new int[n + 1];
            for (int i = 1; i <= m; i++) {
                int pre = dp[0];
                for (int j = 1; j <= n; j++) {
                    int cur = dp[j];
                    if (s.charAt(i - 1) == t.charAt(j - 1)) {
                        dp[j] = pre + 1;
                    } else {
                        dp[j] = dp[j - 1];
                    }
                    pre = cur;
                }
            }
            return dp[n] == m ? true : false;
        }
    }
    

    双指针

    class Solution {
        public boolean isSubsequence(String s, String t) {
            int m = s.length();
            int n = t.length();
            int i = 0, j = 0;
            while (i < m && j < n) {
                if (s.charAt(i) == t.charAt(j)) {
                    i++;
                }
                j++;
            }
            return i == m;
        }
    }
    
  • 不同的子序列(115)

    给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

    动态规划

    // 二维dp
    class Solution {
        public int numDistinct(String s, String t) {
            // dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]
            int[][] dp = new int[s.length() + 1][t.length() + 1];
            // 初始化dp数组
            // 空字符串是任意字符串的子串
            for (int i = 0; i < s.length(); i++) {
                dp[i][0] = 1;
            }
            // dp
            for (int i = 1; i <= s.length(); i++) {
                for (int j = 1; j <= t.length(); j++) {
                    if (s.charAt(i - 1) == t.charAt(j - 1)) {
                        // 用s[i - 1]来匹配,即dp[i - 1][j - 1]
                        // 不用s[i - 1]来匹配,即dp[i - 1][j]
                        dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                    } else {
                        dp[i][j] = dp[i - 1][j];
                    }
                }
            }
            return dp[s.length()][t.length()];
        }
    }
    
    // 一维dp
    class Solution {
        public int numDistinct(String s, String t) {
            int[] dp = new int[t.length() + 1];
            // 初始化dp数组
            // 空字符串是任意字符串的子串
            dp[0] = 1;
            // dp
            for (int i = 1; i <= s.length(); i++) {
                int tmp1 = dp[0];
                for (int j = 1; j <= t.length(); j++) {
                    int tmp2 = dp[j];
                    if (s.charAt(i - 1) == t.charAt(j - 1)) {
                        dp[j] = tmp1 + dp[j];
                    } else {
                        dp[j] = dp[j];
                    }
                    tmp1 = tmp2;
                }
            }
            return dp[t.length()];
        }
    }
    

    递归+记忆化搜索

    class Solution {
        // 用于存储已经计算过的子问题的结果
        int[][] memo;
        public int numDistinct(String s, String t) {
            memo = new int[s.length()][t.length()];
            for (int i = 0; i < s.length(); i++) {
                for (int j = 0; j < t.length(); j++) {
                    memo[i][j] = -1;
                }
            }
            return dfs(s, t, s.length() - 1, t.length() - 1);
        }
    
        public int dfs(String s, String t, int i, int j){
            // 字符串t遍历结束,完成匹配
            if (j < 0) {
                return 1;
            }
            // 字符串s遍历结束,未完成匹配
            if (i < 0) {
                return 0;
            }
            // 已经计算过的直接返回结果
            if (memo[i][j] != -1) {
                return memo[i][j];
            }
            if (s.charAt(i) == t.charAt(j)) {
                memo[i][j] = dfs(s, t, i - 1, j - 1) + dfs(s, t, i - 1, j);
            } else {
                memo[i][j] = dfs(s, t, i - 1, j);
            }
            return memo[i][j];
        }
    }
    
  • 两个字符的删除操作(583)

    给定两个单词 word1word2 ,返回使得 word1word2 相同所需的最小步数

    动态规划

    class Solution {
        public int minDistance(String word1, String word2) {
            int len1 = word1.length();
            int len2 = word2.length();
            // dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,要达到相等,需要删除元素的最少次数。
            int[][] dp = new int[len1 + 1][len2 + 1];
            // 初始化dp
            for (int i = 0; i <= len1; i++) {
                dp[i][0] = i;
            } 
            for (int i = 0; i <= len2; i++) {
                dp[0][i] = i;
            } 
            // dp
            for (int i = 1; i <= len1; i++) {
                for (int j = 1; j <= len2; j++) {
                    if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1];
                    } else {
                        // 情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
    					// 情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
    					// 情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
                        dp[i][j] = Math.min(dp[i - 1][j - 1] + 2, Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
                    }
                }
            }
            return dp[len1][len2];
        }
    }
    

    长度相加 - 最大公共子序列长度*2

    class Solution {
        public int minDistance(String word1, String word2) {
            int len1 = word1.length();
            int len2 = word2.length();
            // dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2 的最长公共子序列。
            int[][] dp = new int[len1 + 1][len2 + 1];
            // dp
            for (int i = 1; i <= len1; i++) {
                for (int j = 1; j <= len2; j++) {
                    if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1] + 1;
                    } else {
                        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                    }
                }
            }
            return len1 + len2 - dp[len1][len2] * 2;
        }
    }
    
  • 编辑距离(72)

    给你两个单词 word1word2请返回将 word1 转换成 word2 所使用的最少操作数

    可以使用插入、删除、替换

    动态规划

    class Solution {
        public int minDistance(String word1, String word2) {
            int len1 = word1.length();
            int len2 = word2.length();
            // dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]
            int[][] dp = new int[len1 + 1][len2 + 1];
            // 初始化dp
            for (int i = 0; i <= len1; i++) {
                dp[i][0] = i;
            }
            for (int i = 0; i <= len2; i++) {
                dp[0][i] = i;
            }
            // dp
            for (int i = 1; i <= len1; i++) {
                for (int j = 1; j <= len2; j++) {
                    if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                        // 若相同则无需操作
                        dp[i][j] = dp[i - 1][j - 1];
                    } else {
                        // 若不同,则需要增、删、改
                        // 操作1:word1删除/word2增加 dp[i][j] = dp[i - 1][j] + 1
                        // 操作2:word2删除/word1增加 dp[i][j] = dp[i][j - 1] + 1
                        // 操作3:改元素 dp[i][j] = dp[i - 1][j - 1] + 1
                        dp[i][j] = Math.min(dp[i - 1][j - 1] + 1, Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
                    }
                }
            }
            return dp[len1][len2];
        }
    }
    
回文
  • 回文子串(647)

    给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

    动态规划

    class Solution {
        public int countSubstrings(String s) {
            int len = s.length();
            int res = 0;
            if (s == null || len < 1) return 0;
            //dp[i][j]:s字符串下标i到下标j的字串是否是一个回文串,即s[i, j]
            boolean[][] dp = new boolean[len][len];
    
            for (int j = 0; j < len; j++) {
                for (int i = 0; i <= j; i++) {
                    //当两端字母一样时,才可以两端收缩进一步判断
                    if (s.charAt(i) == s.charAt(j)) {
                        //i--,j++,即两端收缩之后i,j指针指向同一个字符或者i超过j了,必然是一个回文串
                        if (j - i <= 2) {
                            dp[i][j] = true;
                        } else {
                            //否则通过收缩之后的字串判断
                            dp[i][j] = dp[i + 1][j - 1];
                        }
                    }
                    //两端字符不一样,不是回文串
                    else {
                        dp[i][j] = false;
                    }
                }
            }
            //遍历每一个字串,统计回文串个数
            for (int i = 0; i < len; i++) {
                for (int j = 0; j < len; j++) {
                    if (dp[i][j]) res++;
                }
            }
            return res;
        }
    }
    

    中心扩展法

    // 如果回文长度是奇数,那么回文中心是一个字符;如果回文长度是偶数,那么中心是两个字符。
    class Solution {
        public int countSubstrings(String s) {
            int len = s.length();
            // 单个字符都是回文
            int res = len;
            for (int i = 0; i < len; i++) {
                // 以s[i]为中心
                int left = i - 1;
                int right = i + 1;
                while (left >= 0 && right < len && s.charAt(left--) == s.charAt(right++)) {
                    res++;
                }
                // 以s[i],s[i+1]为中心
                left = i;
                right = i + 1;
                while (left >= 0 && right < len && s.charAt(left--) == s.charAt(right++)) {
                    res++;
                }
            }
            return res;
        }
    }
    
  • 最长回文子序列(516)

    给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

    动态规划

    // 二维dp
    class Solution {
        public int longestPalindromeSubseq(String s) {
            int len = s.length();
            char[] c = s.toCharArray();
            // dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]
            int[][] dp = new int[len][len];
            // 遍历i的时候要从下到上遍历,保证下一行的数据是经过计算的
            for (int i = len - 1; i >= 0; i--) {
                // 单个字符位置都是回文
                dp[i][i] = 1;
                for (int j = i + 1; j < len; j++) {
                    if (c[i] == c[j]) {
                        // 如果s[i]与s[j]相同,同时加入,那么dp[i][j] = dp[i + 1][j - 1] + 2;
                        dp[i][j] = dp[i + 1][j - 1] + 2;
                    } else {
                        // 如果s[i]与s[j]不相同,分别加入s[i]、s[j]看哪一个可以组成最长的回文子序列
                        dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
                    }
                }
            }
            return dp[0][len - 1];
        }
    }
    
    // 一维dp
    class Solution {
        public int longestPalindromeSubseq(String s) {
            int len = s.length();
            char[] c = s.toCharArray();
            // dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]
            int[] dp = new int[len];
            // 从下到上、从左到右遍历
            for (int i = len - 1; i >= 0; i--) {
                dp[i] = 1;
                int pre = 0;
                for (int j = i + 1; j < len; j++) {
                    int tmp = dp[j];
                    if (c[i] == c[j]) {
                        // pre = dp[i - 1][j + 1]
                        dp[j] = pre + 2;
                    } else {
                        // dp[j]在赋新值之前存储的值对应dp[i + 1][j]
                        dp[j] = Math.max(dp[j], dp[j - 1]);
                    }
                    pre = tmp;
                }
            }
            return dp[len - 1];
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值