【编程练习】最长专项!

433 阅读5分钟

子数组连续

子串连续

子序列不连续

动态规划算法:与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。

1. 最长无重复子数组 - 滑动窗口

image.png

子数组:连续!

  1. 滑动窗口,设置左右两个指针,初始值都为0
  2. 用set来存储目前滑窗内的数据
  3. 当元素不重复时,则将不重复元素添加到set,右指针+1
  4. 当元素重复时,则进行结算,统计此窗口的长度,r-l+1
  5. 左指针+1,移除左指针的前一个元素,保证窗口是从左指针开始
  6. 最终在所有结算的最长长度中再选出最长的
import java.util.*;


public class Solution {
    /**
     * 
     * @param arr int整型一维数组 the array
     * @return int整型
     */
    public int maxLength (int[] arr) {
        
        int r = 0;
        int res = 0;
        Set<Integer> mySet = new HashSet<Integer>();
        for(int l=0;l<arr.length;l++){
            if(l !=0 ){
                mySet.remove(arr[l-1]);
            }
            
            while(r<arr.length && !mySet.contains(arr[r])){
                mySet.add(arr[r]);
                r++;
            }
            res = Math.max(res,r-l);
        }
        return res;
    }
}

2. 最长公共子串 - 动态规划

练习地址

image.png

  1. dp[i+1][j+1]用于记录以字符str1.charAt(i)和str2.charAt(j)结尾的最长子串的长度。dp数组的第一行和第一列使用默认值0。
  2. dp只是记载长度,因此两个中间变量用于记录,最长长度最长长度时str1的结束索引,以助于最终获得最长子串的具体字符串。
  3. 如果两个结尾的字符相等,则dp[i+1][j+1] = dp[i][j]+1;
  4. 如果两个结尾的字符不相等,则dp[i+1][j+1] = 0;
import java.util.*;


public class Solution {
    /**
     * longest common substring
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @return string字符串
     */
    public String LCS (String str1, String str2) {
        // dp[i][j]用于记录以字符str1.charAt(i-1)和str2.charAt(j-1)结尾的最长子串的长度
        int[][] dp = new int[str1.length()+1][str2.length()+1];
        //dp只是记载长度,因此两个中间变量用于记录,最长长度 及 对应的索引
        int maxLength = 0;
        int lastIndex = 0;
        //dp[0][j]和dp[i][0] 都使用默认值0
        for(int i=0;i<str1.length();i++){
            for(int j=0;j<str2.length();j++){
            //如果两字符相等,则dp[i+1][j+1] = dp[i][j]+1;
                if(str1.charAt(i) == str2.charAt(j)){
                    dp[i+1][j+1] = dp[i][j]+1;
                    if(dp[i+1][j+1]>maxLength){
                        maxLength = dp[i+1][j+1];
                        lastIndex = i;
                    }
                }else{
                //如果两字符不相等则dp[i+1][j+1] = 0
                    dp[i+1][j+1] = 0;
                }
            }
        }
        return str1.substring(lastIndex-maxLength+1,lastIndex+1);
    }
}

3. 最长回文子串 - 动态规划

练习地址

image.png

  1. boolean dp[l][r] 记录索引l到索引r的字符串是否为回文字符串。
  2. dp对角线都为true。
  3. 2-3个字符时,只要第一个和最后一个字符相同,也是true (因为dp[l+1][r-1]需要r至少比l大3个,才不至于迭代的时候l>=r)
  4. 如果l的字符和r的字符相等,且dp[l+1][r-1]==true 则dp[l][r]=true。否则为false。
import java.util.*;

public class Solution {
    public int getLongestPalindrome(String A, int n) {
        // write code here
        char[] arr = A.toCharArray();
        boolean[][]  dp = new boolean[n][n];
        //初始化:对角线都为true
        for(int i=0;i<n;i++){
            dp[i][i] = true;
        }
        int max = 1;
        for(int r=1;r<n;r++){
            for(int l=0;l<r;l++){
            
            //2-3个字符时,只要第一个和最后一个字符相同,也是true
                if(r - l <= 2){
                    dp[l][r] = arr[l] == arr[r];
                }else{
                    if(arr[l] == arr[r] && dp[l+1][r-1]){
                        dp[l][r] = true;
                    }else{
                        dp[l][r] = false;
                    }
                }
                
                if(dp[l][r]){
                    max = Math.max(max,r-l+1);
                }
            }
        }
        return max;
    }
}

4. 最长公共子序列

4.1 最长公共子序列①

4.2 最长公共子序列② -动态规划

练习地址

image.png

  1. dp[i+1][j+1]表示以s1.charAt(i)和s2.charAt(j)结尾的字符串的最长公共子序列的长度。
  2. dp第一行和第一列使用0作为默认值。
  3. 如果s1.charAt(i)和s2.charAt(j)相等,则dp[i+1][j+1] = dp[i][j]+1;否则dp[i+1][j+1] = Math.max(dp[i][j+1],dp[i+1][j])。
  4. 根据最长公共子序列的长度,反过来求得子序列实际值。
  5. 从尾部开始遍历,如果尾部字符相等,则两个尾部指针同时减1,否则比较dp上面和左边的值,哪边值更大,就往哪边移动。
import java.util.*;


public class Solution {
    /**
     * longest common subsequence
     * @param s1 string字符串 the string
     * @param s2 string字符串 the string
     * @return string字符串
     */
    public String LCS (String s1, String s2) {
        // dp[i+1][j+1]表示以s1.charAt(i)和s2.charAt(j)结尾的字符串的最长公共子序列的长度
        int[][] dp = new int[s1.length()+1][s2.length()+1];
        for(int i=0;i<s1.length();i++){
            for(int j=0;j<s2.length();j++){
                if(s1.charAt(i) == s2.charAt(j)){
                    dp[i+1][j+1] = dp[i][j]+1;
                }else{
                    dp[i+1][j+1] = Math.max(dp[i][j+1],dp[i+1][j]);
                }
            }
        }
        
        //根据最长公共子序列的长度,反过来求得子序列实际值
        StringBuffer sb = new StringBuffer();
        int l1 = s1.length()-1;
        int l2 = s2.length()-1;
        //从尾部开始遍历,如果尾部字符相等,则两个尾部指针同时减1
        //否则比较dp上面和左边的值,哪边值更大,就往哪边移动
        while(l1>=0&&l2>=0){
            if(s1.charAt(l1) == s2.charAt(l2)){
                sb.append(s1.charAt(l1));
                l1--;
                l2--;
            }else{
            //因为dp[i+1][j+1]代表的是(i,j),所以比较的时候要+1
                if(dp[l1+1][l2] > dp[l1][l2+1]){
                    l2--;
                }else{
                    l1--;
                }
            }
        }
        if(sb.length() == 0){
            return "-1";
        }
        return sb.reverse().toString();
    }
}

5. 最长递增子序列

练习地址

image.png

  1. 我们将大问题拆分为小问题,把数组缩短,再慢慢加长直到完整。如示例 1 中 原数组 arr = [2, 1, 5, 3, 6, 4, 8, 9, 7] 我们把最初的子问题定为 [2, 1],下一个子问题既往后加长一位 [2, 1, 5],以此类推。
  2. 为了能在遍历完子问题后精确地在原数组 arr 中找出组成最长递增子序列 LCS 的元素,我们可以使用“标号”的方法,在 arr 中组成 LCS 的元素上标上序号,比如示例 1 中 arr = [2, 1, 5, 3, 6, 4, 8, 9, 7], LCS = [1, 3, 4, 8, 9],LCS[0] = arr[1],LCS[1] = arr[3],LCS[2] = arr[5]。所以如何标号就是这个问题的关键。
    图片说明
  3. 如何标号呢?我们需要新增一个数组 temp,每当子问题增加一个元素 e 时,e 就与 temp 最后一个元素就进行比较,如果 e 比 temp 的最后一个元素大,则直接在 temp 最后面添加 e;反之,则在 temp 中从左往右寻找第一个比 e 大的数,并用 e 替换之。然后 e 在 temp 中的索引就是我们要找的标号,我们将标号存起来,继续下一个子问题。
    图片说明
  4. 在 nums 中标完号后,为了满足题目要求的字典序最小,我们需要从后往前遍历,标号从大到小,倒着填入 LCS 中,最后我们获得结果 LCS。
    图片说明
import java.util.*;


public class Solution {
    /**
     * retrun the longest increasing subsequence
     * @param arr int整型一维数组 the array
     * @return int整型一维数组
     */
    public int[] LIS (int[] arr) {
        int n = arr.length;
        //temp用于存放子序列
        int[] temp = new int[n];
        //lens[i]用于存放以arr[i]结尾时,最长递增子序列的长度
        int[] lens = new int[n];
        if(n == 0){
            return new int[0];
        }
        //初始化
        int tempIndex = 0;
        temp[tempIndex] = arr[0];
        lens[0] = 1;
        
        for(int i=1;i<n;i++){
            if(arr[i]>temp[tempIndex]){
                tempIndex++;
                lens[i] = tempIndex+1;
                temp[tempIndex] = arr[i];
            }else{
                //替换temp中第一个比arr[i]大于等于的值
                int left = 0;
                int right = tempIndex;
                while(left <= right){
                    // 注意这里 left <= right 而不是 left < right,我们要替换的是第一个比 arr[i] 大的元素
                    int mid = (left+right)/2;
                    if(temp[mid] >= arr[i]){
                        right = mid -1;
                    }else{
                        left = mid +1;
                    }
                }
                temp[left] = arr[i];
                lens[i] = left+1;
            }
        }
        
        int[] res = new int[tempIndex+1];
        for(int i = n-1;i>=0;i--){
            if(lens[i] == tempIndex+1){
                res[tempIndex] = arr[i];
                tempIndex--;
            }
        }
        return res;
    }
}

6. 最长连续子序列

练习地址

image.png

  1. 进行数组排序。
  2. 如果arr[i]-arr[i-1]==1 ,则计数+1。
  3. 如果arr[i] == arr[i-1],则跳过不进行计数。
  4. 如果arr[i]-arr[i-1] > 1,则重新开始计数。
  5. 从所有的计数中选出最大的数。
import java.util.*;

public class Solution {
    /**
     * max increasing subsequence
     * @param arr int整型一维数组 the array
     * @return int整型
     */
    public int MLS (int[] arr) {
        // write code here
        Arrays.sort(arr);
        if(arr.length == 0){
            return 0;
        }
        int res = 1;
        int temp = 1;
        for(int i=1;i<arr.length;i++){
            if(arr[i]-arr[i-1]==1){
                temp++;
            }else if(arr[i] == arr[i-1]){
                continue;
            }else{
                res = Math.max(res,temp);
                temp = 1;
            }
        }
        return Math.max(res,temp);
    }
}

7. 最长公共前缀

练习地址

  1. 循环数组中字符串,依次比较第一个字符串中的字符是否在每个字符串的对应位置出现。
  2. 如果都有出现,则在最终结果中append该字符。
  3. 否则终止循环,返回。
import java.util.*;


public class Solution {
    /**
     * 
     * @param strs string字符串一维数组 
     * @return string字符串
     */
    public String longestCommonPrefix (String[] strs) {
        // write code here
        if(strs.length == 0){
            return "";
        }
        StringBuffer sb = new StringBuffer();
        for(int i=0;i<strs[0].length();i++){
            char x = strs[0].charAt(i);
            for(int j=1;j<strs.length;j++){
                if(strs[j].length() > i){
                    if(strs[j].charAt(i) != x){
                        return sb.toString();
                    }
                }else{
                    return sb.toString();
                }
            }
            sb.append(x);
        }
        return sb.toString();
        
    }
}

8. 最长括号子串

练习地址