前言:
如果说代码有灵魂,那么它的灵魂一定是算法,因此,想要写出优美的程序,核心算法是必不可少的,少年,你渴望力量吗,想掌握程序的灵魂吗❓❗️那么就必须踏上这样一条漫长的道路,我们要做的,就是斩妖除魔,打怪升级!当然切记不可走火入魔,每日打怪,拾取经验,终能成圣!开启我们今天的斩妖之旅吧!✈️✈️
题目:
给定一个字符串 s ,请你找出其中不含有重复字符的 最长连续子字符串 的长度。
示例:
提示:
- 0 <= s.length <= 5 * 104
- s 由英文字母、数字、符号和空格组成
解法一:
思路:
这道题目让我们求出最长子串的长度,我们先来使用暴力来解决这道题目,要判断是否有重复字符的最长子串我们首先会想到用双指针来解决。
1、设置左右指针,让右指针前进,将右指针遍历过得元素用哈希表记录,当右指针指向的元素在哈希表里出现了两次,则右指针停止前进。
2、这时记录出本次无重复子串的长度,然后左指针向后移动一位,右指针回退到左指针位置,再将哈希表清空,重新开始记录。
3、这样不断枚举出所有不重复子串,最后就能得到最长子串,这里需要注意的是,右指针长度不能超过数组s的长度。
代码实现:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int hash[128] = { 0 };//用数组模拟哈希表
int len = s.size();//求出字符串s的长度
if(len == 1) return 1;
int ret = 0, right = 0, left = 0;//设置左右指针以及ret记录最长子串
while(left < len)//左指针在数组范围内
{
again://防止右指针多加一次
hash[s[right]]++;//将右指针的值在哈希表中对应位置++
while(hash[s[right]] > 1)//表示右指针遇到了重复值
{
left++;//遇到重复值本次结束将左指针向右移动一位开启下一轮比较
ret = max(ret, right - left + 1);//记录下本次无重复子串长度
right = left;//右指针回退到左指针位置
memset(hash, 0, sizeof(hash));//将hash表重置
goto again;//防止right多加一次
}
if(right != len)//保证右指针不越界
right++;
}
return ret;//返回最长子串即可
}
};
这样的暴力似乎还不错,但是有没有更好的写法了呢?其实是有的,在我们的暴力的基础上进行优化。
解法二:
思路:
如果你理解透了暴力解法,那么就可以在此基础上进行进阶—— 滑动窗口 问题:
1、其实我们在使用右指针时,回退那一步操作完全没有必要进行,因为回退之后再次向后遍历,遍历到的新的重复字符一定是要比上一次右指针最远位置相等或者更远的,因为遇到了两个相等的字符,我们右指针就会停下,那么右指针前面扫描过的区域就一定不会存在重复字符问题,所以我们并不需要回退这部操作。
2、既然不需要回退这步操作,那么我们哈希表也不用每次使用都要清零再记录了,当左指针移动之前,我们就将左指针对应位置的哈希值-1,这样就能继续保证左右指针区间内无重复字符了。
3、第二步操作有些问题,当数组为大量重复数据时,如果仅仅判断一次,那么就会造成长度误判,所以只要我们右指针指向元素的哈希值>1,那么我们就一直执行第二步操作。
3、左指针移动之后,我们就与上一次记录的不重复子串进行比较,返回较大值。这些做完之后,开始新一轮查询,右指针自增。当右指针遍历完整个数组后,最长子串也就出来了。
代码实现:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int hash[128] = { 0 };//数组代替哈希表
int left = 0, right = 0, n = s.size();
int ret = 0;//返回值
while(right < n)
{
hash[s[right]]++;
while(hash[s[right]] > 1)//防止重复数据
{
hash[s[left++]]--;
}
ret = max(ret, right - left + 1);
right++;
}
return ret;
}
};
这题使用双指针暴力写法也不是很简单,尤其是在右指针回退那里,一不小心就容易出错,而我们使用滑动窗口来解决问题,虽然代码量很少,但是却很不好想,滑动双指针的题做多了可能你觉得滑动双指针不难,可是我认为,我们能想到这题使用滑动双指针更加重要。