Java 最长子串、子序列问题

Java 算法之最长子串、最长公共子序列、最长公共子串、最长回文串

1. 无重复字符的最长子串(对应力扣题3)

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

可以使用「滑动窗口」来解决这个问题:

  1. 我们使用两个指针表示字符串中的某个子串(或窗口)的左右边界,其中左指针代表着窗口的左边界「枚举子串的起始位置」,而右指针代表窗口的右边界
    在每一步的操作中,我们会将左指针向右移动一格,表示我们开始枚举下一个字符作为起始位置,然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。在移动结束后,这个子串就对应着以左指针开始的,不包含重复字符的最长子串。我们记录下这个子串的长度;
    在枚举结束后,我们找到的最长的子串的长度即为答案。
  2. 判断重复字符:
    在上面的流程中,我们还需要使用一种数据结构来判断 是否有重复的字符,常用的数据结构为哈希集合(即 Java 中的 HashSet)。在左指针向右移动的时候,我们从哈希集合中移除一个字符,在右指针向右移动的时候,我们往哈希集合中添加一个字符。

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-by-leetc-2/
来源:力扣(LeetCode)

    //最长无重复字符子串
    public static int longestNoRepeatSubstring(String str){
        int len = str.length();
        Set<Character> set = new HashSet<>();
        int slidePtr = 0;
        int maxLen = 0;
        for(int i = 0; i < len; i++){
            while(slidePtr < len && !set.contains(str.charAt(slidePtr))){
                set.add(str.charAt(slidePtr));
                slidePtr++;
            }
            maxLen = Math.max(maxLen, slidePtr-i);
            set.remove(str.charAt(i));
        }
        return maxLen;
    }

2. 最长公共子序列

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

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
    两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
  • 示例 1:
输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace" ,它的长度为 3 。

解题思路

求两个数组或者字符串的最长公共子序列问题,肯定是要用 动态规划

首先,区分两个概念:子序列可以是不连续的;子数组(子字符串)需要是连续的;
另外,动态规划也是有套路的:单个数组或者字符串要用动态规划时,可以把动态规划 dp[i] 定义为 nums[0:i] 中想要求的结果;当两个数组或者字符串要用动态规划时,可以把动态规划定义成两维的 dp[i][j],其含义是在 A[0:i]B[0:j] 之间匹配得到的想要的结果。

  1. 状态定义
    比如对于本题而言,可以定义dp[i][j]表示 text1[0:i-1] text2[0:j-1] 的最长公共子序列。 (注:text1[0:i-1] 表示的是 text1 的 第 0 个元素到第 i-1 个元素,两端都包含)
    之所以 dp[i][j] 的定义不是 text1[0:i]text2[0:j] ,是为了方便当 i = 0 或者 j = 0 的时候,dp[i][j]表示的为空字符串和另外一个字符串的匹配,这样 dp[i][j] 可以初始化为 0.

  2. 状态转移方程

知道状态定义之后,我们开始写状态转移方程。

text1[i - 1] == text2[j - 1] 时,说明两个子字符串的最后一位相等,所以最长公共子序列又增加了 1,所以 dp[i][j] = dp[i - 1][j - 1] + 1;举个例子,比如对于 ac 和 bc 而言,他们的最长公共子序列的长度等于 a 和 b 的最长公共子序列长度 0 + 1 = 1。
text1[i - 1] != text2[j - 1] 时,说明两个子字符串的最后一位不相等,那么此时的状态 dp[i][j] 应该是 dp[i - 1][j] dp[i][j - 1] 的最大值。举个例子,比如对于 ace 和 bc 而言,他们的最长公共子序列的长度等于 ① ace 和 b 的最长公共子序列长度0 与 ② ac 和 bc 的最长公共子序列长度1 的最大值,=1。
综上状态转移方程为:
d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 , 当 t e x t 1 [ i − 1 ] = = t e x t 2 [ j − 1 ] ; dp[i][j] = dp[i - 1][j - 1] + 1, 当 text1[i - 1] == text2[j - 1]; dp[i][j]=dp[i1][j1]+1,text1[i1]==text2[j1];

d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) , 当 t e x t 1 [ i − 1 ] ! = t e x t 2 [ j − 1 ] dp[i][j]=max(dp[i−1][j],dp[i][j−1]), 当 text1[i - 1] != text2[j - 1] dp[i][j]=max(dp[i1][j],dp[i][j1]),text1[i1]!=text2[j1]

  1. 状态的初始化
    初始化就是要看当 i = 0 与 j = 0 时, dp[i][j] 应该取值为多少。

当 i = 0 时,dp[0][j] 表示的是 text1 中取空字符串 跟 text2 的最长公共子序列,结果肯定为 0.
当 j = 0 时,dp[i][0] 表示的是 text2 中取空字符串 跟 text1 的最长公共子序列,结果肯定为 0.
综上,当 i = 0 或者 j = 0 时,dp[i][j] 初始化为 0.

  1. 遍历方向与范围
    由于 dp[i][j] 依赖与 dp[i - 1][j - 1] , dp[i - 1][j], dp[i][j - 1],所以 i 和 j 的遍历顺序肯定是从小到大的。
    另外,由于当 i 和 j 取值为 0 的时候,dp[i][j] = 0,而 dp 数组本身初始化就是为 0,所以,直接让 i 和 j 从 1 开始遍历。遍历的结束应该是字符串的长度为 len(text1)len(text2)

  2. 最终返回结果
    由于 dp[i][j] 的含义是 text1[0:i-1] text2[0:j-1] 的最长公共子序列。我们最终希望求的是 text1 和 text2 的最长公共子序列。所以需要返回的结果是 i = len(text1) 并且 j = len(text2) 时的 dp[len(text1)][len(text2)]

作者:fuxuemingzhu
链接:https://leetcode-cn.com/problems/longest-common-subsequence/solution/fu-xue-ming-zhu-er-wei-dong-tai-gui-hua-r5ez6/
来源:力扣(LeetCode)

 	//最长公共子序列
    public static int longestCommonSequence(String str1, String str2){
        int len1 = str1.length();
        int len2 = str2.length();
        int[][] dp = new int[len1+1][len2+1];
        dp[0][0] = 0;   //Str1和str2长度都是0,则公共子串长度也是0
        for(int i = 1; i < len1+1; i++){
            for(int j = 1; j < len2+1; j++){
                if(str1.charAt(i-1) == str2.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 dp[len1][len2];
    }*/

3. 最长公共子串

思路与解法与 最长公共子序列 基本一致

状态方程为:
d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 , 当 t e x t 1 [ i − 1 ] = = t e x t 2 [ j − 1 ] ; dp[i][j] = dp[i - 1][j - 1] + 1, 当 text1[i - 1] == text2[j - 1]; dp[i][j]=dp[i1][j1]+1,text1[i1]==text2[j1];

d p [ i ] [ j ] = 0 , 当 t e x t 1 [ i − 1 ] ! = t e x t 2 [ j − 1 ] dp[i][j]=0, 当 text1[i - 1] != text2[j - 1] dp[i][j]=0,text1[i1]!=text2[j1]

//最长公共子串
    public static int longestCommonString(String str1, String str2){
        int len1 = str1.length(), len2 = str2.length();
        int[][] dp = new int[len1+1][len2+1];
        int maxLen = 0;
        int endIndexOfStr1 = 0;
        dp[0][0] = 0;
        for(int i = 1; i < len1; i++){
            for(int j = 1; j <len2; j++){
                if(str1.charAt(i-1) == str2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1] + 1;
                    if(maxLen < dp[i][j]){
                        maxLen = dp[i][j];
                        endIndexOfStr1 = i-1;
                    }
                }//else
                    //dp[i][j] = 0;
                //}
            }
        }
        //return str1.substring(endIndexOfStr1-maxLen+1, endIndexOfStr1);
        return maxLen;
    }*/

4. 最长回文子串(对应力扣题5)

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

思路与算法

从对应的子串开始不断地向两边扩展。如果两边的字母相同,就可以继续扩展;如果两边的字母不同,就可以停止扩展,因为在这之后的子串都不能是回文串了。

我们枚举所有的「回文中心」并尝试「扩展」,直到无法扩展为止,此时的回文串长度即为此「回文中心」下的最长回文串长度。我们对所有的长度求出最大值,即可得到最终的答案。

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/
来源:力扣(LeetCode)

//最长回文串
    public static String longestPalindromicSubstring(String str){
        int maxLen = 0;
        int len = str.length();
        int endIndex = 0;
        for(int i = 0; i < len; i++){
            int single = getLength(str, i, i);
            int double_ = getLength(str, i, i+1);
            if(maxLen < Math.max(single, double_)){
                maxLen = Math.max(single, double_);
                endIndex = i + (maxLen)/2;
            }
        }
        return str.substring(endIndex-maxLen+1, endIndex+1);
    }
    public static int getLength(String str, int left, int right){
        while(left >= 0 && right < str.length() && str.charAt(left)==str.charAt(right)){
            left--;
            right++;
        }
        return right - left - 1;
    }

5. 代码整理

import java.util.*;

class Solution {
    public static void main(String[] args) {
        String str = "abcdecghij";
        String s1 = "abcdefhjas";
        String s2 = "deabcdejsfhmmn";
        String s3 = "abcdefggfedopmab";
        //System.out.println(longestNoRepeatSubstring(str));  //result: 7,decghij
        //System.out.println(longestCommonSequence(s1, s2));  //result: 7, abcdejs
        //System.out.println(longestCommonString(s1, s2));    //result: 5, abcde
        System.out.println(longestPalindromicSubstring(s3));    //result: 8, defggfed
    }

    //最长回文串
    public static String longestPalindromicSubstring(String str){
        int maxLen = 0;
        int len = str.length();
        int endIndex = 0;
        for(int i = 0; i < len; i++){
            int single = getLength(str, i, i);
            int double_ = getLength(str, i, i+1);
            if(maxLen < Math.max(single, double_)){
                maxLen = Math.max(single, double_);
                endIndex = i + (maxLen)/2;
            }
        }
        return str.substring(endIndex-maxLen+1, endIndex+1);
    }
    public static int getLength(String str, int left, int right){
        while(left >= 0 && right < str.length() && str.charAt(left)==str.charAt(right)){
            left--;
            right++;
        }
        return right - left - 1;
    }

    /*
    //最长无重复字符子串
    public static int longestNoRepeatSubstring(String str){
        int len = str.length();
        Set<Character> set = new HashSet<>();
        int slidePtr = 0;
        int maxLen = 0;
        for(int i = 0; i < len; i++){
            while(slidePtr < len && !set.contains(str.charAt(slidePtr))){
                set.add(str.charAt(slidePtr));
                slidePtr++;
            }
            maxLen = Math.max(maxLen, slidePtr-i);
            set.remove(str.charAt(i));
        }
        return maxLen;
    }*/

    /*
    //最长公共子串
    public static int longestCommonString(String str1, String str2){
        int len1 = str1.length(), len2 = str2.length();
        int[][] dp = new int[len1+1][len2+1];
        int maxLen = 0;
        int endIndexOfStr1 = 0;
        dp[0][0] = 0;
        for(int i = 1; i < len1; i++){
            for(int j = 1; j <len2; j++){
                if(str1.charAt(i-1) == str2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1] + 1;
                    if(maxLen < dp[i][j]){
                        maxLen = dp[i][j];
                        endIndexOfStr1 = i-1;
                    }
                }//else
                    //dp[i][j] = 0;
                //}
            }
        }
        //return str1.substring(endIndexOfStr1-maxLen+1, endIndexOfStr1);
        return maxLen;
    }*/

    /*
    //最长公共子序列
    public static int longestCommonSequence(String str1, String str2){
        int len1 = str1.length();
        int len2 = str2.length();
        int[][] dp = new int[len1+1][len2+1];
        dp[0][0] = 0;   //Str1和str2长度都是0,则公共子串长度也是0
        for(int i = 1; i < len1+1; i++){
            for(int j = 1; j < len2+1; j++){
                if(str1.charAt(i-1) == str2.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 dp[len1][len2];
    }*/

}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值