LeetCode第 3 题:无重复字符的最长子串(C++)

LeetCode链接
在这里插入图片描述

滑动窗口

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set<char> c_set;
	    int len = 0;
	    // 指向字串首尾的
	    int beg = 0, end = 0;
	    int n = s.size();

	    for (; beg < n; ++beg){
		    if (beg != 0){
			    // 左指针向右移动一格
			    c_set.erase(s[beg-1]);
		    }
		
		    // 判断是否需要进行子串延长
		    while (end < n && !c_set.count(s[end])){
			    c_set.insert(s[end]);
			    ++end;
		    }
		    len = max(len, int(end-beg));
        }
        return len;
    }
};

但是上述代码的左指针每次向右移动一格,其实产生了更多的计算:

  • 假设字符串为 abcbd,遍历子串(也就是c_set里的字符)依次是(带颜色子串):

  • abcbd

  • abcbd

  • abcbd ,此时的while循环中end=3,s[3]=b,b在子串中出现过,于是进入下一次for循环(beg=1),erase字串中第一个元素a,将左指针向右移动一位指向b;

  • 但是,左指针向右移动一位指向 b 是多余的,因为刚才检查过 b 在子串中出现过,所以得到的字串(bc)必然是上一个字串的子集

  • abcbd

  • abcbd

  • abcbd

  • 所以此时将左指针直接移动到出现过的字符的下一个位置(比如上面出现过的字符为 b ,就应该将左指针移动到 b 的下一位 c)就可以省略掉子集的计算:
    abcbd
    abcbd

再作改进,需要记录下查找到的字符的位置,使用泛型算法find():

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
    	vector<char> vec;
	    int len = 0;

	    auto rit = s.begin();
	    while(rit != s.end()){
		    // 子串存入vector,vector的size即为字串长度
		    while ( rit != s.end() && find(vec.begin(), vec.end(), *rit) == vec.end()){
			    vec.push_back(*rit);
			    ++rit;
		    }
		    len = max(len, int(vec.size())); //当前子串长度
		    if (rit != s.end()){
                //重复元素之前的元素都可以删除
			    vec.erase(vec.begin(), find(vec.begin(), vec.end(), *rit)+1);
		    }	
        }
        return len;   
    }
};

但是查找的操作效率比较一般。

再进行优化(个人是最喜欢这个版本):

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.empty())   return 0;
        int len = 0;
        int l = 0, r = 0;//窗口的左右边界
        while(r < s.size()){
            //窗口中每新增一个元素s[r],都要查询之前的窗口区间中是否已经存在该元素
            //遍历上一次的左边界到当前位置(之前的窗口区间),如果出现元素与当前位置元素重复,则需要更新左边界
            for(int i = l; i < r; ++i){
                if(s[i] == s[r]){
                    l = i+1;    
                    break;
                }
            }
            len = max(len, r-l+1);
            ++r;
        }
        return len;
    }
};

更简洁版本:

数组大小128是ascii码的原因,char向int的转变;

i,j分别代表子串的左右边界(第一个和最后一个字符):没有重复字符时调整右边界(向右移一格),出现重复字符时调整左边界(移至重复字符的下一位),j - i + 1为字串长度。

假如此刻边界如下:
pwwkew
此时 i → \rightarrow w, j → \rightarrow k

  • j 下一刻即将指向 e ,而且字串中不会出现字符与 e 重复,便移动右边界 j 指向 e :pwwkew
  • j 再下一刻即将指向 w ,字串中的字符 w 会重复,应当调整左边界 i 指向字串中的字符 w 的下一个位置:pwwkew

数组中的元素是用来存储字符的位置的,并且会不断更新:

一开始:a[p] = 1, a[w] = 2;
当右边界移动到第二个字符 w 时, a[w] = 3,覆盖掉之前的值。
接下来:a[k] = 4, a[e] = 5 ;
最后的a[w] = 6也会覆盖之前的值。
 class Solution {
public:
    int lengthOfLongestSubstring(string s) {
    	
	    int a[128] = {0};
	    int ans = 0;
	    int i = 0;
	    for (int j = 0; j < s.size(); j++) {
	    	// a[s[j]]存储s[j]在s中的位置(从1开始):下标+1,(并且随着遍历遇到重复字符时会更新值)
	    	// 不出现重复字符:i = max(i, a[s[j]]) = i,即不改变左边界
	    	// 出现重复字符时:i= max(i, a[s[j]]) =  a[s[j]],即将左边界指向重复字符的下一个位置
	    	// 注意数组中维护的值是递增的,与下标有关
	    	// 即根据左边界的值和将要处理的字符映射到数组中的值的大小关系,就可以知道要不要调整左边界
	    	// 上述映射关系:字符作为数组下标,开局全初始化为0,如果该字符是第一次出现,那么映射的值必为0,也就是
	    	// 该值肯定 <= i,反之如果该字符在正在处理的子串中出现过,那映射的值必然会 > i。
		    i = max(i, a[s[j]]);
		    a[s[j]] = j + 1;
		    ans = max(ans, j - i + 1);
	    }

        return ans;   
    }
};

套滑动窗口的模板:

leetcode 第 76 题:最小覆盖子串(C++)_qq_32523711的博客-CSDN博客

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> window;
        int left= 0, right = 0;
        int len = 0;
        while(right < s.size()){
            auto c = s[right];
            ++right;
            ++window[c];

            while(window[c] > 1){
                auto c = s[left];
                ++left;
                --window[c];
            }
            len = max(len, right - left);
        }
        return len;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值