前言
记录一些最近遇到的DP问题,并给出解法。只会记录我知道的最优解法(一般是时间最优),并贴出源码。有OJ的还会贴上地址。
1. 求字符串的最长不重复子串的长度
举例:
abcabcbb
,返回abc
;bbbbb
,返回b
;pwwkew
,返回wke
。注意pwke
是子序列,不是子串。
问题分析:
记字符串存放在字符数组cs
中,遍历cs
的下标为i
。分析可知,想求截止到i
的最长不重复子串的长度,只要从i
向左,找到第1个重复的字符,其右边的数标记为left
,则所求长度就是i-left+1
与之前求得的结果中的最大值。举例说明:
- 当字符串为
abcdace
,而i
=5时,left
应该为3。此时i-left+1=3
,而之前求得的最长不重复子串的长度是4>3,所以i=5
时的结果是4。
经分析可知,该算法需要在每一次遍历时,向左查找0~i
个不等的元素,所以时间复杂度是O(n2),空间复杂度O(1)。
上述方案尚有优化的空间,是因为在寻找left
时采用了简单的遍历。经过分析可以知道left的变化是有规律的。只要知道与cs[i]相同的字符在上一次出现的位置,记为k,则left=k+1(k>=left的前提下)。所以,我们需要一个数组,记录所有元素最后出现的下标,就可以将查找left的时间从O(n)降到O(1),此时时间复杂度是O(n),空间复杂度是O(1)(增加了长度为256的定长辅助数组,所以空间复杂度不变)。
实现代码如下:
public class Solution {
public int lengthOfLongestSubstring(String str) {
if(str==null||str.length()==0)
return 0;
char[] cs = str.toCharArray();
int[] charMap = new int[256];
Arrays.fill(charMap , -1);
int max = 0;
int left = 0;
for(int i=0;i<cs.length;i++){
if(charMap[cs[i]]>=left)
left = charMap[cs[i]]+1;
int tmp = i-left+1;
if(tmp>max)
max = tmp;
charMap[cs[i]]=i;
}
return max;
}
}
代码在LeetCode上运行通过。
2. 两个字符串的最长公共子串
最朴素的解法,就是遍历两个字符串的所有子串,然后比较是否相等。两层嵌套循环,再加上字符串的比较,时间复杂度是O(m*n*n)
,其中m
与n
是两个字符串的长度,且m>n
。肯定有优化的方法。
考虑使用二维数组help[i][j]
,其中i
是str1
的下标,j
是str2
的下标,help[i][j]
记录的是str1的第i个字符与str2的第j个字符是否相等。举例说明,字符串abccba
与deccb
,其help
数组为:
很明显,最长公共子串就是help中对角线上全为1的最长序列,就是图中的红色部分。这种解法使用了一个m*n
的辅助数组,将时间复杂度降到了O(MN)。
其实,help
数组并不需要m*n
,只需要n
就够了。如果将help
数组记录成如下方式:
这样一来,其实我们在每轮遍历时,只需要记录help的一行就可以了,如图中红框所示。下一趟遍历可以覆盖掉上一趟的结果,只要将有可能是最长的位置记录下来即可,这里要使用两个变量end
与length
。end
记录help数组当前的下标,length
记录对应的公共子串的长度。在最后一轮遍历结束后,只需要返回从end-length+1
到end
的子串,就可以了。
下面是代码:
public String getLongestCommonSubstring(String str1, String str2){
//check input parameters
if(str1==null||str2==null)
return null;
//find out the bigger string and the smaller one
String big = str1.length()>=str2.length()?str1:str2;
String small = str1.length()<str2.length()?str1:str2;
//get the end of the longest common substring in small
int[] help = new int[small.length()];
int end = 0;
int length = 0;
for(int i=0;i<big.length();i++){
for(int j=small.length()-1;j>=0;j--){
if(big.charAt(i)==small.charAt(j)){
if(j==0)
help[j]=1;
else{
help[j]=help[j-1]+1;
if(help[j]>length){
length = help[j];
end = j;
}
}
}else
help[j]=0;
}
}
//get the longest common substring and return it
if(length==0)
return null;
else
return small.substring(end-length+1,end+1);
}
代码在华为OJ上测试通过,当然要符合他们的规范。