跳至内容

拾光小记

算法-获取最长不重复子串

题目(中等难度)

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(nnn) 空间复杂度为:O(n * n) 需要一个nn的数组保存所有的子串

上面代码可以进一步优化,将保存子串的数组去掉,获取到子串时,直接进行重复校验即可。空间复杂度变为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)