力扣3. 无重复字符的最长子串

题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:

输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:

输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
示例 4:

输入: s = “”
输出: 0

提示:

0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
滑动窗口参考链接:https://www.cnblogs.com/huansky/p/13488234.html

1.暴力穷举方法
【优化】暴力穷举法需要用两层for循环,来寻找子串,另外还需要一个for循环来检查当前字母和前面的是否重复。
【优化】第三层for循环可以使用hashmap表存储当前元素,然后检查是否相同,省去一个for循环
对于"pwwkew"来说,最长子串的长度是3,而“pwke”是一个子序列,不是子串。
“”,长度是0,而" "长度是1,前面处理的逻辑有问题,首先应该保证用最简单的方法,正确的处理逻辑,其次,在考虑优化版本,之后解决问题
几乎所有的问题,都可以用暴力穷举方,来解决问题。但是暴力穷举,思想简单,耗时长。
那么这时候就需要利用空间复杂度来换取时间复杂度,比如加入一些数据结构。
我认为效率最高的代码,一定是空间复杂度低,且时间复杂度也低。但是这两者好像天平一样,没有办法达到都低,或者很难达到。
当然了,这是我的拙见,毕竟,我还是个刚入门的小白,人外有人,天外有天嘛。
暴力穷举法,就是找到所有不重复子串,之后求一个最大值。
1.最外层for循环i,遍历字符串0~len(s)-1
2.内层for循环j,遍历从i开始向后找,找到一个最长的不重复子串。
3.怎么判断是不重复的子串,还需要使用for循环从后向前依次比较,是否重复。

int lengthOfLongestSubstring(string s) {
    //从逻辑上来讲,size() 成员函数似乎应该返回整形数值,或是无符号整数。但事实上,size 操作返回的是 string::size_type 类型的值。
    //string 类类型和许多其他库类型都定义了一些配套类型(companion type)。通过这些配套类型,库类型的使用就能与机器无关(machine-independent)。size_type 就是这些配套类型中的一种
    //它定义为与 unsigned 型(unsigned int 或 unsigned long)具有相同的含义,而且可以保证足够大能够存储任意 string 对象的长度。为了使用由 string 类型定义的 size_type 类型是由 string 类定义。任何存储string的size操作结果的变量必须为string::size_type 类型。特别重要的是,不要把size的返回值赋给一个 int 变量。
    int maxLen = 0;
    int slen = strlen(s.c_str()); //此处,将字符串转换成char数组,strlen求字符串长度

    for(int i = 0; i < slen; i++) {
        string str = "";
        bool is_repeat = false;

        for(int j = i; j < slen; j++) {
            char chr = s[j];
            int sublen = strlen(str.c_str());

            //检查chr是否在之前的子串中已经存在
            for(int k = 0; k < sublen; k++) {
                if(chr == str[k]) {
                    is_repeat = true;
                    break;
                }
            }

            //不能在是否出现重复元素在子串的if后面记录最长子串,因为有可能就没有重复的。
            if(is_repeat) {
                break;
            }

            //随时更新不重复子串,并记录子串长度,且更新最大子串长度。
            //此处求出所有最大子串的长度
            str += chr;
            sublen += 1;

            if(sublen > maxLen) {
                maxLen = sublen;
            }
        }
    }

    return maxLen;
}

2.避免一些不必要的子串计算
例如"abcabc",对于这个字符串,只需要检查abc的长度即可,剩下的不需要检查
因为这里a到a中间有3个字符,b到b中间有三个字符,c也是,不知道大家发现这个规律了吗?
所以abc子串检查完成之后,应该设置最外层循环直接跳过下一个abc或ab或a的检查,直接检查在下一个。
对于abcabcbb这个子串,那么i=0,j=3发现第一个不重复的,那么向后检查,i=3,j=6的时候不相同,那么i=3开始检查,利用这个规律
减少一部分时间

int lengthOfLongestSubstring(string s) {
    //从逻辑上来讲,size() 成员函数似乎应该返回整形数值,或是无符号整数。但事实上,size 操作返回的是 string::size_type 类型的值。
    //string 类类型和许多其他库类型都定义了一些配套类型(companion type)。通过这些配套类型,库类型的使用就能与机器无关(machine-independent)。size_type 就是这些配套类型中的一种
    //它定义为与 unsigned 型(unsigned int 或 unsigned long)具有相同的含义,而且可以保证足够大能够存储任意 string 对象的长度。为了使用由 string 类型定义的 size_type 类型是由 string 类定义。任何存储string的size操作结果的变量必须为string::size_type 类型。特别重要的是,不要把size的返回值赋给一个 int 变量。
    map<int, int> mymap;
    int maxLen = 0;
    int slen = strlen(s.c_str()); //此处,将字符串转换成char数组,strlen求字符串长度
    int i = 0;

    while(i < slen) {
        int len = 0;
        bool is_exist = false;

        for(int j = i; j < slen; j++) {
            char chr = s[j];

            //检查chr是否在之前的子串中已经存在
            if(mymap.find(chr) == mymap.end()) {
                mymap[chr] = j;
                len++;
            } else {
                int index = mymap[chr];
                is_exist = true;

                for(int k = j; k < slen; k++) {
                    if(s[index] != s[k]) {
                        break;
                    }

                    index++;
                }

                i = index - 1;
            }

            //随时更新不重复子串,并记录子串长度,且更新最大子串长度。
            //此处求出所有最大子串的长度

            if(len > maxLen) {
                maxLen = len;
            }

            if(is_exist) {
                break;
            }
        }

        mymap.clear();
        i++;
    }

    return maxLen;
}

3.滑动窗口
滑动窗口协议(sliding window protocol),该协议是 tcp协议 的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。
该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认。因此该协议可以加速数据的传输,提高网络吞吐量。
滑动窗口算法其实和这个是一样的,只是用的地方场景不一样,可以根据需要调整窗口的大小,有时也可以是固定窗口大小。
简而言之,滑动窗口算法在一个特定大小的字符串或数组上进行操作,而不在整个字符串和数组上操作,这样就降低了问题的复杂度,从而也达到降低了循环的嵌套深度。其实这里就可以看出来滑动窗口主要应用在数组和字符串上。
滑动窗口是通过两个指针来控制的。左指针和右指针
我们不妨以示例一中的字符串 abcabcbb 为例,找出从每一个字符开始的,不包含重复字符的最长子串,那么其中最长的那个字符串即为答案。
我们可以发现,当k是不断递增的,那么rk也是不断递增或者不变的,因为从k到rk没有重复元素,而k+1到rk也是如此,所以可以继续想rk+1不重复子串结果。
unordered_map和map的用法是相同的,但是如果要擦除元素和不断加入元素,unordered_map的时间复杂度和空间复杂度都会大大低于map
map和set两种容器的底层结构都是红黑树,所以容器中不会出现相同的元素,
因此count()的结果只能为0和1,可以以此来判断键值元素是否存在(当然也可以使用find()方法判断键值是否存在)。
拿map<key,value>举例,find()方法返回值是一个迭代器,成功返回迭代器指向要查找的元素,
失败返回的迭代器指向end。count()方法返回值是一个整数,1表示有这个元素,0表示没有这个元素。

int lengthOfLongestSubstring(string s) {
    unordered_map<int, int> mymap;
    int maxLen = 0;
    int len = strlen(s.c_str());     //当前传入字符串的长度
    int k = 0;                   //滑动窗口开始的位置
    int rk = k + 1;            //滑动窗口结束的位置
    int currLen = 1;         //当前活动窗口中字符串的长度
    mymap[s[k]] = 1;

    while(k < len) {
        //rk一直向右移动,直到出现重复的元素
        //无论是使用count和find,结果都是相同的,速度应该也差不多。
        while(rk < len) {
            if(mymap.find(s[rk]) == mymap.end()) {
                //if(!mymap.count(s[rk])) {
                mymap[s[rk]] = 1;
                rk++;
                currLen++;
            } else {
                break;
            }
        }

        if(currLen > maxLen) {
            maxLen = currLen;
        }

        //这步操作,类似于优化,当rk超过或等于len-1时,其实滑动窗口已经取完所有可能的串,而且当前滑动窗口中都是不重复的子串。
        //当计算完成本次结果后,可以直接退出。因为,所有的组合都已经计算过了。
        if(rk >= len - 1) {
            break;
        }

        //k每次向右移动一格,直到剩下的元素没有重复的
        //关于移除元素,可以用for循环,将k不断右移,找到第一次重复的元素,就停止,在继续迭代,这样的话,能比当前这个版本稍微快点,
        //但是我感觉速度提升有限,也可以像当前这样,k移动一次,看下rk是否还重复,若rk重复,则k在继续移动一次。
        mymap.erase(s[k]);
        k++;
        currLen--;
    }

    return maxLen;
}
int main() {
    string s = "abcabcbb";
    int maxLen = lengthOfLongestSubstring(s);
    printf("%d\n", maxLen);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值