【滑动窗口】LeetCode:30串联所有单词的子串

简介: 【滑动窗口】LeetCode:30串联所有单词的子串

题目

给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。

s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。

例如,如果 words = [“ab”,“cd”,“ef”], 那么 “abcdef”, “abefcd”,“cdabef”, “cdefab”,“efabcd”, 和 “efcdab” 都是串联子串。 “acdbef” 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

示例 1:

输入:s = “barfoothefoobarman”, words = [“foo”,“bar”]

输出:[0,9]

解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。

子串 “barfoo” 开始位置是 0。它是 words 中以 [“bar”,“foo”] 顺序排列的连接。

子串 “foobar” 开始位置是 9。它是 words 中以 [“foo”,“bar”] 顺序排列的连接。

输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

输入:s = “wordgoodgoodgoodbestword”, words = [“word”,“good”,“best”,“word”]

输出:[]

解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。

s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。

所以我们返回一个空数组。

示例 3:

输入:s = “barfoofoobarthefoobarman”, words = [“bar”,“foo”,“the”]

输出:[6,9,12]

解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。

子串 “foobarthe” 开始位置是 6。它是 words 中以 [“foo”,“bar”,“the”] 顺序排列的连接。

子串 “barthefoo” 开始位置是 9。它是 words 中以 [“bar”,“the”,“foo”] 顺序排列的连接。

子串 “thefoobar” 开始位置是 12。它是 words 中以 [“the”,“foo”,“bar”] 顺序排列的连接。

参数范围

1 <= s.length <= 104

1 <= words.length <= 5000

1 <= words[i].length <= 30

words[i] 和 s 由小写英文字母组成

滑动窗口

时间复杂度: O(nlen) n是s的长度,len是words[i].length。iWindowWidth = len * words.size();

两层循环:第一层时间复杂度O(len),第二层:O(n/len),所以两层循环的时间复杂度是O(n)。

如何判断s[i,i+iWindowWidth) 是 串联子串

将words依次放到一个mHas 中,依次mStrToCount[sSub]–,如果value为0,则移除key。

sSub为s[i,i+len) s[i+len,i+2len) s[i+2len,i+3*len) …

如果mSub为空,说明是串联子串。

s[i,i+iWindowWidth)和s[i+len,i+len+iWindowWidth)

前者多:s[i,i+len)

后者多:s[i+iWindowWidth,i+len+iWindowWidth)

这是滑动窗口的基础。mSub加上前者,减去后者。

代码

核心代码

template<class KEY>
class CKeyCount
{ 
public:
  void Add(const KEY& key, int iCount)
  {
    Cnt[key] += iCount;
    if (0 == Cnt[key])
    {
      Cnt.erase(key);
    }
  }
  std::unordered_map<KEY, int> Cnt;
};
class Solution {
public:
  vector<int> findSubstring(string s, vector<string>& words) {
    const int len = words.front().length();
    const int iWindowWidth = len * words.size();
    CKeyCount<string> mStrToCount;
    for (const auto& w : words)
    {
      mStrToCount.Add(w, 1);
    } 
    vector<int> vRet;   
    for (int i = 0; (i < len)&&(i + iWindowWidth <= s.length()); i++)
    {
      int j = i;
      auto mHas = mStrToCount;
      for (; j < i + iWindowWidth; j += len )
      {
        mHas.Add(s.substr(j, len), -1);
      }
      if (mHas.Cnt.empty())
      {
        vRet.emplace_back(j-iWindowWidth);
      }
      for (; j + len <= s.length(); j += len)
      {
        mHas.Add(s.substr(j, len), -1);
        mHas.Add(s.substr(j-iWindowWidth, len), 1);
        if (mHas.Cnt.empty())
        {
          vRet.emplace_back(j - iWindowWidth+len);
        }
      }
    }
    return vRet;
  }
};

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{
  string s;
  vector<string> words;
  {
    Solution sln;
    s = "barfoothefoobarman", words = { "foo", "bar" };
    auto res = sln.findSubstring(s, words);
    Assert(vector<int>{0, 9}, res);
  }
  {
    Solution sln;
    s = "wordgoodgoodgoodbestword", words = { "word", "good", "best", "word" };
    auto res = sln.findSubstring(s, words);
    Assert(vector<int>{}, res);
  }
  {
    Solution sln;
    s = "barfoofoobarthefoobarman", words = { "bar", "foo", "the" };
    auto res = sln.findSubstring(s, words);
    Assert(vector<int>{6, 9, 12}, res);
  }
}

2023年4月

class Solution {
public:
vector findSubstring(string s, vector& words) {
m_r = words.size();
m_c = words[0].size();
if (s.length() < m_rm_c)
{
return vector();
}
vector vRet;
for (int i = 0; i < m_c; i++ )
{
findSubstring(vRet,i,s.c_str() + i, words);
}
return vRet;
}
void findSubstring(vector& vRet,int iBeginIndex,string s, vector& words) {
std::multiset mLess(words.begin(), words.end()), mMore;
int i = 0;
for (; i + 1 < m_r; i++)
{
DelOrAdd(mLess, mMore, s.substr(m_ci, m_c));
}
for (; i < s.length() / m_c; i++)
{
DelOrAdd(mLess, mMore, s.substr(m_ci, m_c));
int iPreIndex = i - m_r;
if (iPreIndex >= 0)
{
DelOrAdd(mMore, mLess, s.substr(m_ciPreIndex, m_c));
}
if (0 == mLess.size())
{
vRet.emplace_back(iBeginIndex + m_c*(iPreIndex + 1));
}
}
}
void DelOrAdd(std::multiset& mDel, std::multiset& mAdd, const string& s)
{
auto it = mDel.find(s);
if (mDel.end() != it)
{
mDel.erase(it);
return;
}
mAdd.emplace(s);
}
int m_r, m_c;
};


扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法C++ 实现。



相关文章
|
2天前
|
算法 搜索推荐
力扣每日一题 6/15 滑动窗口
力扣每日一题 6/15 滑动窗口
5 1
|
2天前
|
存储 算法 测试技术
力扣经典150题第三十三题:最小覆盖子串
力扣经典150题第三十三题:最小覆盖子串
6 1
|
17天前
|
算法
【LeetCode刷题】滑动窗口解决问题:串联所有单词的子串(困难)、最小覆盖子串(困难)
【LeetCode刷题】滑动窗口解决问题:串联所有单词的子串(困难)、最小覆盖子串(困难)
|
2天前
|
算法 索引
力扣随机一题 位运算/滑动窗口/数组
力扣随机一题 位运算/滑动窗口/数组
10 0
|
2天前
|
算法 测试技术 索引
力扣经典150题第三十二题:串联所有单词的子串
力扣经典150题第三十二题:串联所有单词的子串
5 0
|
2天前
|
算法
力扣经典150题第二十一题:反转字符串中的单词
力扣经典150题第二十一题:反转字符串中的单词
6 0
|
2天前
|
算法
力扣经典150题第十九题:最后一个单词的长度
力扣经典150题第十九题:最后一个单词的长度
6 0
|
15天前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-2
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
15天前
|
算法 C++
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题-1
【数据结构与算法】:关于时间复杂度与空间复杂度的计算(C/C++篇)——含Leetcode刷题
|
17天前
|
索引
【LeetCode刷题】二分查找:山脉数组的峰顶索引、寻找峰值
【LeetCode刷题】二分查找:山脉数组的峰顶索引、寻找峰值