3. 无重复字符的最长子串
题目
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
思路
模拟查找最长子串,依次以较左边起点为开始,找子串直到遇见有重复字符,然后依次起点右移,重复步骤。
- 抽象出一个滑动窗口,子串以左边为起点,右边指针扩展至第一个不重复字符或者最后一个不重复字符,采用hash集合简化窗口内字符的查找
- 细节处理:左起点右移的元素删除,右指针的边界和元素定义(是否归属子串,决定子串长度的计算式)
解法:滑动窗口,hashset
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len = s.length();
if (len < 2)
return len;
int maxLen = 0;
int right = 0; //滑动窗口的右指针
unordered_set<char> set;
for (int i = 0; i < len; ++i) { //滑动窗口的左起点
if (i != 0) //窗口右移时,删除旧起点的元素
set.erase(s[i-1]);
while(right < len && !set.count(s[right])) {//向右扩展窗口
set.insert(s[right]);
++right;
}
maxLen = max(right-i, maxLen); //右指针减左起点即长度,用max()
}
return maxLen;
}
};
//go version 1
func lengthOfLongestSubstring(s string) int {
len := len(s)
if len < 2 {
return len
}
charMap := make(map[byte]int)
beginIndex := 0
currLen := 0
maxLen := 0
for i := 0; i < len; i++ {
if index, ok := charMap[s[i]]; ok && index >= beginIndex { //字符出现过,并且在当前字符串
if currLen > maxLen {
maxLen = currLen
}
beginIndex = index + 1 //更新字符串的起点
if beginIndex < i { //更新字符串的现有长度
currLen = i - beginIndex + 1
} else {
currLen = 1
}
} else { //字符没有出现过,或者出现过但是不在当前字符串中
currLen++
}
charMap[s[i]] = i //增加字符的映射,或者更新已有字符的下标
}
//长度为1的字符串
if currLen > maxLen {
maxLen = currLen
}
return maxLen
}
//go version 2
func lengthOfLongestSubstring(s string) int {
len := len(s) //短路处理
if len < 2 {
return len
}
charMap := make(map[byte]int)
var begin = 0
var maxLen, tmpLen = 0, 0
for i := 0; i<len; i++ {
if charIndex, has := charMap[s[i]]; has {
tmpLen = i - begin
if tmpLen > maxLen { //长度更新
maxLen = tmpLen
}
for j := begin; j<charIndex+1; j++ { //历史字符删除
delete(charMap, s[j])
}
begin = charIndex+1 //update begin
}
charMap[s[i]] = i
}
tmpLen = len - begin
if tmpLen > maxLen {
maxLen = tmpLen
}
return maxLen
}
//go version 2+
func lengthOfLongestSubstring(s string) int {
len := len(s)
if len < 2 {
return len
}
charMap := make(map[byte]int)
var begin = 0
var maxLen, tmpLen = 0, 0
for i := 0; i<len; i++ {
if charIndex, has := charMap[s[i]]; has && charIndex >= begin { //若重复字符在当前字符串内,更新下标等
tmpLen = i - begin
if tmpLen > maxLen {
maxLen = tmpLen
}
begin = charIndex+1 //update begin
}
charMap[s[i]] = i
}
tmpLen = len - begin
if tmpLen > maxLen {
maxLen = tmpLen
}
return maxLen
}
30. 串联所有单词的子串
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:输入:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
输出:[]
思路
示例中有两个细节没有体现:
1:匹配的子串可能重叠,并不是严格不相交。
2:子串并不是以单词长度为移动步长找到的。
移动窗口做,找符合条件的起点,再往后延伸找整个子串。在找到符合的起点之后才操作窗口的内容,每个新窗口都有新的hashmap记录窗口内容,所以移动窗口时无需删除旧窗口的无效内容。因为每个有效起点重建一个hash_map,数据量太大时,可能会引起超时,但此题没有该问题。
毋庸置疑,本题涉及string的一系列操作,需要适时使用容器的函数,极其相关函数的区别,博文后已附上必要的函数使用方法。
解法:移动窗口,两个unordered_map对比匹配子串
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> res;
int num = words.size();
if (num == 0)
return res;
int wordLen = words[0].length();
int len = s.length();
if (len < wordLen*num)
return res;
unordered_map<string, int> wordMap;
for (string & str : words)
++wordMap[str];
for (int i = 0; i <= len - wordLen*num; ++i) { //有等于号!,每次移动一格而不是一个单词长度
string current = s.substr(i, wordLen);
if (!wordMap.count(current)) //words里没有该单词
continue;
unordered_map<string, int> resMap; //记录res中单词情况
++resMap[current];
int right = i+wordLen, resNum = 1;
while (resNum < num) {
string next = s.substr(right, wordLen);
if (!wordMap.count(next) //words里没有该单词
|| resMap[next] == wordMap[next]) //words里有该单词,但是个数已够
break;
++resMap[next];
right += wordLen;
++resNum;
}
if (resNum == num) //符合条件,左窗口右移一个单词长度
res.push_back(i);
}
return res;
}
};
附
获取子串:
s.substr(startIndex, subLen); 取得s从startIndex下标开始的subLen个的字符作为子串
容易混淆的查找函数:
1. size_t find (const char* s, size_t pos = 0) const;
//在当前字符串的pos索引位置开始,查找子串s,返回找到的位置索引, -1表示查找不到子串
2. size_t find (char c, size_t pos = 0) const;
//在当前字符串的pos索引位置开始,查找字符c,返回找到的位置索引, -1表示查找不到字符
3. size_t rfind (const char* s, size_t pos = npos) const;
//在当前字符串的pos索引位置开始,反向查找子串s,返回找到的位置索引, -1表示查找不到子串
4. size_t rfind (char c, size_t pos = npos) const;
//在当前字符串的pos索引位置开始,反向查找字符c,返回找到的位置索引,-1表示查找不到字符
5. size_t find_first_of (const char* s, size_t pos = 0) const;
//在当前字符串的pos索引位置开始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符
6. size_t find_first_not_of (const char* s, size_t pos = 0) const;
//在当前字符串的pos索引位置开始,查找第一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
7. size_t find_last_of(const char* s, size_t pos = npos) const;
//在当前字符串的pos索引位置开始,查找最后一个位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
8. size_t find_last_not_of (const char* s, size_t pos = npos) const;
//在当前字符串的pos索引位置开始,查找最后一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到子串
#include <iostream> #include <vector> using namespace std; int main() { string s = "wordgoodgoodgoodbestword" , str = "good"; int i = s.find_first_of(str, 0) ; int j = s.find(str, 0) ; cout<< i << endl << j<<endl; return 0; }
输出是
1 //查的是good中字符o第一次出现在第1位
4 //查的是good子串第一次出现在第4位