【Leetcode】无重复字符的最长子串

暴力解法,枚举所有子字符串组合

输入:长度[0,n]的字符串

耗时过长---

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len = s.size();
        int ans = 1;
        
        if(s.empty()){
            return 0;
        }
        
        for(int i = 0; i < len-1; i++){
            for(int j = i+1; j < len; j++){
                if(allUnique(s, i, j)){
                    ans = max(ans, j-i+1);
                }
            }
        }
        return ans;
    }
    
    bool allUnique(string ss, int start, int end){
        std::set<char> ss_set;
        for(int i = start; i <= end; i++){
            char ch = ss[i];
            if(ss_set.count(ch)){
                return false;
            }
            ss_set.insert(ss[i]);
            
        }
        return true;
    }
};

第二种方法:改进暴力解法

子字符串为[i,j),左闭右开。一旦检测到新加入字符已存在于已有子字符串中,则返回当前子字符串长度,清空子字符串,左边界右移(i+1),重新生成新的子字符串。

依然耗时过长

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len = s.size();
        std::cout << "len is : " << len << std::endl;
        int ans = 1;
        std::set<char> ss_set;
        int begin = 0;
        
        if(s.empty()){
            return 0;
        }
        else{
            
            while(begin < len-1) {
                //std::cout << "begin is : " << begin << std::endl;
                int tmp = 0;
                ss_set.clear();
                for(int j = begin; j < len; j++){
                    if(ss_set.count(s[j])){
                        break;
                    }
                    ss_set.insert(s[j]); 
                    tmp++;
                }
                begin = begin + 1;
                ans = max(ans, tmp);
            }
            
        }
        return ans;
    }

};

第三种:滑窗法

改进第二种方法。子字符串为[i,j),左闭右开。一旦检测到新加入字符已存在于已有子字符串中,则返回当前子字符串长度,删除begin位置的字符,窗口右移(i+1,count-1),新加入字符与窗口中的元素进行对比。

耗时94ms,消耗内存21.3MB

TIP

1. 通过使用 HashSet 作为滑动窗口,我们可以用 O(1) 的时间来完成对字符是否在当前的子字符串中的检查;

2. 滑动窗口是数组/字符串问题中常用的抽象概念。 窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [ij)(左闭,右开)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将 [ij) 向右滑动 11 个元素,则它将变为 [i+1,j+1)(左闭,右开)。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len = s.size();
        std::set<char> ss_set;
        int ans = 1, begin = 0, count = 0, index = 0;
        set<char>::const_iterator iter;
        
        if(s.empty()){
            return 0;
        }
        else{
            
            while(begin < len-1) {
                
                for(int j = index; j < len; j++){
                    if(ss_set.count(s[j])){
                        index = begin + count;
                        break;
                    }
                    ss_set.insert(s[j]); 
                    count++;
                }
                
               // for(iter = ss_set.begin(); iter != ss_set.end(); iter++){
               //     std::cout << *iter << " " ;
               // }
               // std::cout << std::endl;
                
                ss_set.erase(s[begin]);
                begin = begin + 1;
                ans = max(ans, count);
                count = count - 1;
            }
        }
        return ans;
    }

};

滑窗法比较清晰的表达,引入变量i,j表示窗口的范围。

注意 i++ 和 ++i 的区别

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len = s.size();
        std::set<char> ss_set;
        int ans = 1, i = 0, j = 0;if(s.empty()){
            return 0;
        }
        else{
            while(i < len && j < len){
                if(!ss_set.count(s[j])){
                    ss_set.insert(s[j++]);
                    ans = max(ans, j-i);
                }
                else{
                    ss_set.erase(s[i++]);
                }
            }
        }
        return ans;
    }

};

优化滑窗法

我们可以定义字符到索引的映射,而不是使用集合来判断一个字符是否存在。 当我们找到重复的字符时,我们可以立即跳过该窗口。

也就是说,如果 s[j] 在 [i,j) 范围内有与 j′ 重复的字符,我们不需要逐渐增加 i 。 我们可以直接跳过 [ij] 范围内的所有元素,并将 ii 变为 j' + 1j+1。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len = s.size();
        int ans = 0, i = 0, j = 0;
        map<char, int> str_map;
        
        for(j; j < len; j++){
            if(str_map.count(s[j])){
                i = max(i, str_map.find(s[j])->second);
            }
            
            ans = max(ans, j - i + 1);
            str_map[s[j]] = j+1;  //窗口右边界右移
            
        }
        return ans;
    }

};
原文地址:https://www.cnblogs.com/gdut-gordon/p/10590819.html