Java 算法之最长子串、最长公共子序列、最长公共子串、最长回文串
1. 无重复字符的最长子串(对应力扣题3)
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
可以使用「滑动窗口」来解决这个问题:
- 我们使用两个指针表示字符串中的某个子串(或窗口)的左右边界,其中左指针代表着窗口的左边界「枚举子串的起始位置」,而右指针代表窗口的右边界
在每一步的操作中,我们会将左指针向右移动一格,表示我们开始枚举下一个字符作为起始位置,然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。在移动结束后,这个子串就对应着以左指针开始的,不包含重复字符的最长子串。我们记录下这个子串的长度;
在枚举结束后,我们找到的最长的子串的长度即为答案。 - 判断重复字符:
在上面的流程中,我们还需要使用一种数据结构来判断 是否有重复的字符,常用的数据结构为哈希集合(即 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]
之间匹配得到的想要的结果。
-
状态定义
比如对于本题而言,可以定义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. -
状态转移方程
知道状态定义之后,我们开始写状态转移方程。
当 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[i−1][j−1]+1,当text1[i−1]==text2[j−1];
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[i−1][j],dp[i][j−1]),当text1[i−1]!=text2[j−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.
-
遍历方向与范围
由于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)
。 -
最终返回结果
由于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[i−1][j−1]+1,当text1[i−1]==text2[j−1];
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[i−1]!=text2[j−1]
//最长公共子串
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];
}*/
}