【LeetCode 3-中等】无重复字符的最长子串(高清截图)

3. 【中等】无重复字符的最长子串

https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
给定一个字符串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由英文字母、数字、符号和空格组成

【解】

滑动窗口。
一个长度为n的字符串的子串共1/2 n(n+1)个。这个结果是按照起始位置来分类得到的:左端(起始位置)位于0, 1,…, n-1的子串分别有n, n-1,…, 1个。
不难发现,若按照左端升序列举这些子串,则它们的右端(终止位置)也是递增的。于是,便可按照滑动窗口的基本思想,解决此问题。
需要两个数a, b表示子串s[a, b]。初始时,a=b=0。接下来:
·若s[b+1]不在当前子串s[a, b]中重复,则令b自增。
·否则,令a自增。
当b=n-1时,容易验证:若继续枚举下去,统计出的子串长度也不会再增加,算法终止。
可以看出,在执行算法的过程中,a, b都是只增不减的,即:在操作“判定字符s[b+1]是否已存在于当前子串s[a, b]”能够在O(1)内完成的条件下,算法的时间复杂度为O(n),n为给定的字符串s的长度。并且,子串s[a, b]始终保持各个字符都不相同。

【优化】

为了验证子串s[a, b]是否确实每个字符都不相同,最容易想到的实现之一,就是将每个字符都存入一个集合(std::set或std::unordered_set)里。开始时,字符s[0]位于集合内;b自增前,字符s[b+1]要放入该集合;a自增前,字符s[a]要从集合中删去。std::set是采用红黑树实现的,若使用std::set判重,则算法的最坏时间复杂度达到O(n log⁡|Σ| ),其中|Σ|为无重复字符的最长子串的长度,也就是字母表Σ含有的不同字符的个数——英文字母、数字和特殊符号(含空格)的总数。若采用std::unordered_set判重,则总的时间复杂度维持在O(n)。但是此时集合中的每个元素都只是单个ASCII字符,为了查找指定的ASCII字符是否已包含在集合中,而计算哈希并在集合中索引,具有不少不必要的开销。该方案的一份实现代码是:

#include <algorithm>
#include <unordered_set>

class Solution {
    
    
public:
	int lengthOfLongestSubstring(const string& s) {
    
    
		if (s.empty()) return 0;
		size_t a = 0, b = 0;
		unordered_set<char> u{
    
     s[0] };
		size_t m = 1;
		while (b != s.size() - 1) {
    
    
			switch (u.emplace(s[b + 1]).second) {
    
    
			case true:
				++b;
				m = max(m, u.size());
				break;
			case false:
				u.erase(s[a]);
				++a;
				break;
			}
		}
		return m;
	}
};
用时:36 ms;内存占用:14 MB。两项都只优于10%+的用户。

为了缩短判重的常数时间,采用std::bitset来为每个可能出现的字符记录其是否位于子串中。开始时,字符s[0]位于集合内;b自增前,字符s[b+1]要放入该集合(对应位置记为true);a自增前,字符s[a]要从集合中删去(对应位置记为false)。字符串的每一个字符都在7位ASCII的范围内,所以该std::bitset需要的长度不超过128位。根据字符的ASCII码确定其在该std::bitset中的位置。代码:

#include <algorithm>
#include <bitset>

class Solution {
    
    
public:
	int lengthOfLongestSubstring(const string& s) {
    
    
		if (s.empty()) return 0;
		size_t a = 0, b = 0;
		bitset<128> n;
		n[s[0]] = true;
		char c;
		size_t lmax = 1, l = 1;
		while (b != s.size() - 1) {
    
    
			c = s[b + 1];
			switch (n[c]) {
    
    
			case false:
				n[c] = true;
				++b; ++l;
				lmax = max(lmax, l);
				break;
			case true:
				n[s[a]] = false;
				++a; --l;
				break;
			}
		}
		return lmax;
	}
};
优化后,用时:4 ms,超过97.50%的用户;内存占用:6.5 MB,低于99.96%的用户。

【评注】

1、注意特判。本题中,输入空串时,需要单独处理。
2、子串是从原字符串中截取的,也就是说,必须是原串中连续的一段。

在这里插入图片描述

おすすめ

転載: blog.csdn.net/COFACTOR/article/details/121269674
おすすめ