三、无重复字符的最长子串

一、题目描述

无重复字符的最长子串
image

二、问题分析

方式一:暴力破解

1.直接使用遍历方式,从第一个元素开始,每次遍历向后累加一个元素,如果后一个元素和前面已累加的元素不冲突,那么就一直累积。如果冲突了,那么记录下当前的累加元素的个数。

2.最后当按照所有的元素都作为开头遍历一次之后,我们返回其中最长的累积元素个数。

代码实现:

class Solution {
    public int lengthOfLongestSubstring(String s) {

        //长度为空 返回0
        //长度不为空进行判断
        if(s.length()<1) return 0;
        //init hashset
        HashSet set = new HashSet<Character>();
        int maxCnt = 1;
        for(int i=0;i<s.length();i++){
            set.clear();
            set.add(s.charAt(i));
            for(int j=i+1;j<s.length();j++){
                if(!set.contains(s.charAt(j))){
                    set.add(s.charAt(j));
                    maxCnt = maxCnt<set.size()?set.size():maxCnt;
                }else{
                    break;
                }
            }
        }
        return maxCnt;

    }
}

运行分析:

运行时间:103ms 使用内存:39.1m

时间复杂度:O(n^2) : 循环遍历了两次

空间复杂度:O(n) :开辟了一个大小为n的hashset

方式二:伪暴力破解

1.方式一中暴力破解是以每个元素为开头进行遍历的,也就是如果有10个元素,那么外围循环会遍历10次,有n个元素,外围循环就会遍历n次。

2.观察以下例子,元素【a b c b d r f d c】

按照方式一的遍历方法:

【1】第一次遍历及累加情况:从第一个a开始遍历,一共可以累加 a b c 三个元素

【2】第二次遍历及累加情况:从第一个b开始遍历,一共可以累加b c两个元素

【3】第三次遍历及累加情况:从第一个c开始遍历,一共可以累加c b d r f d 六个元素

【4】遍历未完待续

其实我们可以发现,步骤二其实在做步骤一已经做过的事,而且不会出现比步骤一累加元素多的情况。所以我们可以优化这部分。

【优化过程】我们在每次遍历发生冲突的时候,我们需要记录当前是前面的哪一个元素和此时遍历到的元素冲突了。那我们下一次可以从发生冲突的下一个元素开始进行下一次外围遍历。

按照优化后的遍历方法:

【1】第一次遍历及累加情况:从第一个a开始遍历,一共可以累加 a b c 三个元素。记录冲突位置,发现是第一个b和第二个b冲突了。那么我们下一次遍历从第一个b的下一个位置开始遍历。

【2】第二次遍历及累加情况:第一步已知,我们可以从第一个c开始遍历,一共可以累加c b d r f d 六个元素。

记录冲突位置,那我们可以下一次从第一个c的下一个位置开始继续遍历

【3】遍历未完待续。。

代码实现

class Solution {
    public int lengthOfLongestSubstring(String s) {
		//长度为空 返回0
        //长度不为空进行判断
        if(s.length()<1) return 0;
        //init hashset
        HashMap<Character,Integer> map = new HashMap<Character,Integer>();
        int maxCnt = 1;
        for(int i=0;i<s.length();i++){
            map.clear();
            map.put(s.charAt(i),i);
            for(int j=i+1;j<s.length();j++){
                if(!map.keySet().contains(s.charAt(j))){
                    map.put(s.charAt(j),j);
                    maxCnt = maxCnt<map.size()?map.size():maxCnt;
                }else{
                    i = map.get(s.charAt(i));//这里不需要+1 因为执行完子循环的时候,跳出时自动i++
                    break;
                }
            }
        }
        return maxCnt;
    }
}

运行分析

运行时间:117ms 使用内存:38.9m

时间复杂度:O(n^2) : 循环遍历了两次

空间复杂度:O(n) :开辟了一个大小为n的hashmap

方式三:双指针--滑动窗口

我们可以使用双指针来进行进行全局扫描,指针leftright

【1】开始位置left在index=0,right在index=0,此时元素所代表的值是一样的。

【2】开始判断right的下一位元素是否容纳在数组【leftright】之间。如果包含那么尝试将left右移,之后判断是否仍然容纳在当前的【leftright】之间。

【3】如果right下一位元素和数组【leftright】不冲突,那么继续right右移动。

【4】以下都是在进行【2】【3】步骤的重复。直到right达到末尾。并且范围这期间数组【leftright】的最大值。

代码实现

class Solution {
    public int lengthOfLongestSubstring(String s) {
        //长度为空 返回0
        //长度不为空进行判断
        if(s.length()<1) return 0;
        int left=0,right=0,maxCnt=0;
        HashSet<Character> set = new HashSet<Character>(); 
        while(right<s.length()){
            if(!set.contains(s.charAt(right))){
                set.add(s.charAt(right));
                right++;
                maxCnt = maxCnt>=set.size()?maxCnt:set.size();
            }else{
                set.remove(s.charAt(left));
                left++;
            }
        }
        return maxCnt;
    }
}

运行分析:

运行时间:10ms 使用内存:38.6m

时间复杂度:O(n^2) : 循环遍历了一次

空间复杂度:O(n) :开辟了一个大小为n的hashset

posted @ 2021-04-24 16:16  keinojust  阅读(65)  评论(0)    收藏  举报