3. 无重复字符的最长子串(C++题解含VS可运行源码)

1.力扣C++源码

class Solution {
    
    
public:
    int lengthOfLongestSubstring(string s) {
    
    
        map<char, int> hash;
        int ans = 0;
        int i = 0;
        int n = s.length();
        for (int j = 0; j < n; j++) {
    
    
            if (hash.find(s[j]) != hash.end()) {
    
    
                i = max(hash.find(s[j])->second + 1, i);
            }
            hash[s[j]] = j;
            ans = max(ans, j - i + 1);
        }
        return ans;
    }
};

2.题解

(1)解题思路:滑动窗口

我们不妨以示例一中的字符串 abcabcbb 为例,找出从每一个字符开始的,不包含重复字符的最长子串,那么其中最长的那个字符串即为答案。对于示例一中的字符串,我们列举出这些结果,其中括号中表示选中的字符以及最长的字符串:

  • 以 a(b)cabcbb 开始的最长字符串为 a(bca)bcbb;
  • 以 ab©abcbb 开始的最长字符串为 ab(cab)cbb;
  • 以 abc(a)bcbb 开始的最长字符串为 abc(abc)bb;
  • 以 abca(b)cbb 开始的最长字符串为 abca(bc)bb;
  • 以 abcab©bb 开始的最长字符串为 abcab(cb)b;
  • 以 abcabc(b)b 开始的最长字符串为 abcabc(b)b;
  • 以 abcabcb(b) 开始的最长字符串为 abcabcb(b)。

发现了什么?如果我们依次递增地枚举子串的起始位置,那么子串的结束位置也是递增的!这里的原因在于,假设我们选择字符串中的第 k 个字符作为起始位置,并且得到了不包含重复字符的最长子串的结束位置为 rk 。那么当我们选择第 k+1 个字符作为起始位置时,首先从 k+1 到 rk 的字符显然是不重复的,并且由于少了原本的第 k 个字符,我们可以尝试继续增大 rk ,直到右侧出现了重复字符为止。

这样一来,我们就可以使用滑动窗口来解决这个问题了:

  • 我们使用两个指针表示字符串中的某个子串(或窗口)的左右边界,其中左指针代表着上文中「枚举子串的起始位置」,而右指针即为上文中的 rk ;
  • 在每一步的操作中,我们会将左指针向右移动一格,表示
    我们开始枚举下一个字符作为起始位置,然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。在移动结束后,这个子串就对应着
    以左指针开始的,不包含重复字符的最长子串。我们记录下这个子串的长度;
  • 在枚举结束后,我们找到的最长的子串的长度即为答案。

判断重复字符
在上面的流程中,我们还需要使用一种数据结构来判断 是否有重复的字符,常用的数据结构为哈希表(即 C++中的map)。在左指针向右移动的时候,我们从哈希表中移除一个字符,在右指针向右移动的时候,我们往哈希表中添加一个字符。

(2)方法一:滑动窗口

我们使用 哈希表 将字符存储在当前窗口 [i, j)(最初 j = i)中。 然后我们向右侧滑动索引 j,如果它不在 哈希表 中,我们会继续滑动 j。直到 s[j] 已经存在于 哈希表 中。此时,我们找到的没有重复字符的最长子字符串将会以索引 i 开头。如果我们对所有的 i 这样做,就可以得到答案。即,当 s[j] 已经存在于 哈希表 中时,清空哈希表,然后将 i 向后移一位再重复上述操作。

        map<char, int> hash;//定义一个hash表
        int ans = 0;
        int i = 0;//左指针
        int j = 0;//右指针
        int n = s.length();//字符串长度
        while (i < n && j < n) {
    
    
            //hash.find()的用法:
            //关键字查询,找到则返回指向该关键字的迭代器,否则返回指向end的迭代器
            //hash.end(),指向最后一个元素的下一个位置
            if (hash.find(s[j]) == hash.end()) {
    
    
                //hash[s[j++]] = j; // 等价于hash[s[j]]=j;j++; j自增必须要在下一条语句中的j-i之前
                hash[s[j]] = j;//将右指针j出的元素加入哈希表
                j++;
                ans = max(ans, j - i);//最大值函数max(,)
            }
            else {
    
    
                //hash.erase(s[i++]);   // 根据键值删除哈希表中元素。该条语句等价于hash.erase(s[i]);i++;    每回向右滑动一位(此处可以改善,见方法三)  
                hash.erase(s[i]); //删除左指针i指向的元素
                i++;
            }
        }
        return ans;

(3)方法二:优化的滑动窗口

基于上一个方法,当我们找到重复的字符时,我们可以立即跳过该窗口。也就是说,如果 s[j]在 [i, j)范围内有与 j’重复的字符,我们不需要逐渐增加 ii。 我们可以直接跳过 [i,j’] 范围内的所有元素,并将 i变为 j’ + 1。

        map<char, int> hash;
        int ans = 0;
        int n = s.size();
        for (int i = 0, j = 0; j < n; j++) {
    
    
            if (hash.find(s[j]) != hash.end()) {
    
    
                //i = max(hash.find(s[j])->second + 1, i); //当发现重复字符时,i直接跳到重复字符的下一位,改进了方法2中一个字符一个字符滑动窗口的方法
                //i = hash.find(s[j])->second + 1;//错误,要和i比较去最大值
                //之所以要max(,),因为已经存在的元素并没有清除掉,仍然存在哈希表中
                //如果这个元素在指针i之前,那么i就又跳到前面去了,长度(j-i+1)就长了,结果就错了
            }
            hash[s[j]] = j;
            ans = max(ans, j - i + 1);
        }
        return ans;

3.VS可运行源码

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<map>
#pragma warning(disable:4996)
using namespace std;

class Solution {
    
    
public:
    int lengthOfLongestSubstring(string s) {
    
    
        //方法二:滑动窗口
        //我们使用 哈希表 将字符存储在当前窗口[i, j)(最初 j = i)中。
        //然后我们向右侧滑动索引 j,如果它不在 哈希表 中,我们会继续滑动 j。
        //直到 s[j] 已经存在于 哈希表 中。
        //此时,我们找到的没有重复字符的最长子字符串将会以索引 i 开头。
        //如果我们对所有的 i 这样做,就可以得到答案。
        //即,当 s[j] 已经存在于 哈希表 中时,清空哈希表,
        //然后将 i 向后移一位再重复上述操作。
        //map<char, int> hash;//定义一个hash表
        //int ans = 0;
        //int i = 0;//左指针
        //int j = 0;//右指针
        //int n = s.length();//字符串长度
        //while (i < n && j < n) {
    
    
        //    //hash.find()的用法:
        //    //关键字查询,找到则返回指向该关键字的迭代器,否则返回指向end的迭代器
        //    //hash.end(),指向最后一个元素的下一个位置
        //    if (hash.find(s[j]) == hash.end()) {
    
    
        //        //hash[s[j++]] = j; // 等价于hash[s[j]]=j;j++; j自增必须要在下一条语句中的j-i之前
        //        hash[s[j]] = j;//将右指针j出的元素加入哈希表
        //        j++;
        //        ans = max(ans, j - i);//最大值函数max(,)
        //    }
        //    else {
    
    
        //        //hash.erase(s[i++]);   // 根据键值删除哈希表中元素。该条语句等价于hash.erase(s[i]);i++;    每回向右滑动一位(此处可以改善,见方法三)  
        //        hash.erase(s[i]); //删除左指针i指向的元素
        //        i++;
        //    }
        //}
        //return ans;

        //自己写一遍:
        /*map<char, int> hash;
        int ans = 0;
        int i = 0;
        int j = 0;
        int n = s.length();
        while (i < n && j < n) {
            if (hash.find(s[j]) == hash.end()) {
                hash[s[j]] = j;
                j++;
                ans = max(ans, j - i);
            }
            else {
                hash.erase(s[i]);
                i++;
            }
        }
        return ans;*/

        //方法三:优化的滑动窗口
        //基于上一个方法,当我们找到重复的字符时,我们可以立即跳过该窗口。
        //也就是说,如果 s[j]在[i, j)范围内有与 j’重复的字符,
        //我们不需要逐渐增加 ii。 
        //我们可以直接跳过[i,j’] 范围内的所有元素,并将 i变为 j’ + 1。
        map<char, int> hash;
        int ans = 0;
        int n = s.size();
        for (int i = 0, j = 0; j < n; j++) {
    
    
            if (hash.find(s[j]) != hash.end()) {
    
    
                //i = max(hash.find(s[j])->second + 1, i); //当发现重复字符时,i直接跳到重复字符的下一位,改进了方法2中一个字符一个字符滑动窗口的方法
                //i = hash.find(s[j])->second + 1;//错误,要和i比较去最大值
                //之所以要max(,),因为已经存在的元素并没有清除掉,仍然存在哈希表中
                //如果这个元素在指针i之前,那么i就又跳到前面去了,长度(j-i+1)就长了,结果就错了
            }
            hash[s[j]] = j;
            ans = max(ans, j - i + 1);
        }
        return ans;

        //自己写一遍:
        /*map<char, int> hash;
        int ans = 0;
        int i = 0;
        int n = s.length();
        for (int j = 0; j < n; j++) {
            if (hash.find(s[j]) != hash.end()) {
                i = max(hash.find(s[j])->second + 1, i);
            }
            hash[s[j]] = j;
            ans = max(ans, j - i + 1);
        }
        return ans;*/
        
        }
};
int main()
{
    
    
    cout << "请输入一个字符串:";
    string s;
    cin >> s;
    Solution test;
    int ret=test.lengthOfLongestSubstring(s);
    cout << "无重复字符的最长子串长度为:" <<ret;

    system("pause");
	return 0;
}

Guess you like

Origin blog.csdn.net/weixin_45784564/article/details/121436301