3. 无重复字符的最长子串 30. 串联所有单词的子串 滑动窗口 C/GO语言

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位

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值