朝花夕拾

A Development Engineer, a Life Liver, a Hope Holder

题目(中等难度)

image.png

解答

暴力破解

核心思想:将给定字符串的所有子字符串都列出来,然后对每个字符串进行元素重复判断。
假如给定的字符串长度是n,那么本字符串的所有可能子串个数为
以第一个字符开头的子串个数为: n
以第二个字符开头的子串个数为: n-1
..
以最后一个字符开头的子串个数为: 1
所以总共的子串个数为 1 + 2 + … + (n - 1) + n;
可以看到上面是一个等差数列求和,结果为:

实现代码:

// 保存所有可能的子字符串
List<String> subStrs = new ArrayList<>();

// 获得所有子串
for(int i = 0; i < s.length(); i++) {
for(int j = i; j < s.length(); j++) {
subStrs.add(s.substring(i, j + 1));
}
}

int maxLen = 0;

// 检查每个子串,并得到最长无重复子串
for(String subStr : subStrs) {
// 是否有重复KEY
boolean flag = false;

// 字典
Map<Character, Integer> hash = new HashMap<>();
for(int i = 0; i < subStr.length(); i++) {
if(hash.containsKey(subStr.charAt(i))) {
flag = true;
break;
}

hash.put(subStr.charAt(i), i);
}

if(flag) {
// 有重复KEY 舍弃
continue;
}

// 符合要求的字符串
maxLen = Math.max(maxLen, subStr.length());
}


复杂度:

获取所有子字符串的时间复杂度是O(nn)
遍历每个子串的时间复杂度为O(n
nn)
空间复杂度为:O(n
n) 需要一个n*n的数组保存所有的子串

上面代码可以进一步优化,将保存子串的数组去掉,获取到子串时,直接进行重复校验即可。空间复杂度变为O(n)

滑动窗口

定义两个指针[i, j],分别界定窗口的左右区间。窗口大小为size=j-i+1;
初始时:i,j都指向第一个元素,窗口大小为0
接着滑动窗口的右边界,获取一个校验元素m
如果m在窗口中不重复,则将其添加到窗口m,否则调整窗口,将窗口左边界移动到窗口与校验元素的后一个元素。
重复上面过程,直至数组中所有元素都遍历完毕。

case1:
输入:”dvdf”
输出:3
最长子串: “vdf”

执行过程:
image.png

case2:
输入:”dddd”
输出:1
最长子串: “d”

执行过程:
image.png

case3:
输入:”d”
输出:1
最长子串: “d”

image.png

实现代码:

// 左边界指针
int i = 0;
int j = i;
int maxLen = 0;

// 右边界指针
for (; j < s.length(); j++) {
if (i == j) {
// 窗口大小为1 两个指针指向同一个元素,啥也不做,等待
// 右边界j右滑,获取新的校验元素
continue;
}

//窗口[i, j) 这里不不含j,因为这个j现在还没有真正进入窗口
//在进入窗口之前,需要先对其做一个重复校验
for (int k = i; k < j; k++) {
if (s.charAt(k) == s.charAt(j)) {
// 出现重复,需要调整窗口。不过在调整之前,需要更新最长子串长度
// 因为窗口调整前和调整后是两个不同的子串
// 注意这里是j - i,因为j元素目前还是校验元素,并没有真正在窗口中
maxLen = Math.max(maxLen, j - i);

// 出现重复,这时需要调整窗口;调整方法是:将窗口左边界移动到窗口中
// 与校验元素重复的元素后一个位置。当前窗口中的重复元素位置为k
i = k + 1;
break;
}

}
}

// 这里的j = s.length 所以窗口长度计算如下
return Math.max(maxLen, j - i);

复杂度:

最外层要对字符串进行完全遍历,循环内部需要对窗口进行重复判断。所以时间复杂度为O(n*n)
由于整个计算过程只用到了matLen一个变量,所以空间复杂度为O(1)