之前做到一道算法题感觉挺有意思的,题目内容如下:
给你一个字符串str,你必须让找到这个字符串中的最长的正方字符串,正方字符串是以下的形式
str = x + x; where x is any string. Return length of this square string.
Example
str = ababa
Max length square string = 4;
str = abcd
Max length square string = 1;
我思考了一下,认为首先可以用暴力解法。因为要满足正方字符串,必须是x+x,所以突破口可能是找到一个数组里2个相同的字母,再判断2个字母之间的子串和第二字母后面相同长度的子串是否相等。大致的思路为遍历2遍数组,第一遍的下标i从0到length-1,找到一个数组的元素,第二遍从i+1到length-1遍历,找到和第一遍遍历相同的元素,然后计算它们自己的间隔,看间隔子串是否和第二个字母后续相同长度的子串是否一样。这样的时间复杂度应该是o(n^3) (最差情况,2层循环o(n^2),匹配子串最差的情况是o(n))。空间复杂度因为只使用了临时变量,应该只有o(1)
后来再思考了一下,其实寻找位置的时候可以通过先遍历一遍数组,用map记录下每一个字符在哪些位置出现过。后续第二遍计算子串是否匹配时可以少一层遍历的时间复杂度,直接用map定位相同字母的位置。这种方式就是以时间换空间
代码如下:
public static int getSquareString(String str) {
if (str == null || str.length() == 0) {
return 0;
}
int maxLength = 0;
Map<Character, List<Integer>> positionMap = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
positionMap.computeIfAbsent(str.charAt(i), k -> new ArrayList<>());
positionMap.get(str.charAt(i)).add(i);
}
for (int leftIndex = 0; leftIndex < str.length(); leftIndex++) {
for (Integer rightIndex : positionMap.get(str.charAt(leftIndex))) {
int interval = rightIndex - leftIndex;
if (rightIndex + interval < str.length()
&& str.substring(leftIndex, leftIndex + interval)
.equals(str.substring(rightIndex, rightIndex + interval))) {
maxLength = Math.max(maxLength, 2 * interval);
}
}
positionMap.get(str.charAt(leftIndex)).remove((Integer) leftIndex); // leftIndex遍历完了,后面应该也用不上了,可以直接删除
}
return maxLength;
}
public static void main(String[] args) {
System.out.println(getSquareString("ababa"));
System.out.println(getSquareString("abcd"));
System.out.println(getSquareString("dssadassadas"));
System.out.println(getSquareString("aaaaa"));
System.out.println(getSquareString("aaaaabbbb"));
}
这个算法时间复杂度下降至o(n^2),空间复杂度为o(n)