【滑动窗口】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月前
【LeetCode 26】239.滑动窗口最大值
【LeetCode 26】239.滑动窗口最大值
33 1
|
2月前
Leetcode(最后一个单词长度)
这篇文章介绍了两种解决LeetCode第58题的方法,即计算给定字符串中最后一个单词的长度,方法包括翻转字符串和逆向遍历统计。
20 0
|
2月前
【LeetCode 20】151.反转字符串里的单词
【LeetCode 20】151.反转字符串里的单词
19 0
|
2月前
【LeetCode 04】滑动窗口法总结
【LeetCode 04】滑动窗口法总结
19 0
|
4月前
|
算法
LeetCode第58题最后一个单词的长度
LeetCode第58题"最后一个单词的长度"的解题方法,通过从字符串末尾向前遍历并计数非空格字符,直接得出最后一个单词的长度。
LeetCode第58题最后一个单词的长度
|
4月前
|
算法 JavaScript Python
【Leetcode刷题Python】79. 单词搜索和剑指 Offer 12. 矩阵中的路径
Leetcode第79题"单词搜索"的Python解决方案,使用回溯算法在给定的二维字符网格中搜索单词,判断单词是否存在于网格中。
45 4
|
4月前
|
Python
【Leetcode刷题Python】生词本单词整理
文章提供了一个Python程序,用于帮助用户整理和排版生词本上的单词,包括去除重复单词、按字典序排序,并按照特定的格式要求进行打印排版。
42 3
|
4月前
|
Python
【Leetcode刷题Python】318. 最大单词长度乘积
本文提供了LeetCode题目318的Python编程解决方案,题目要求在一个字符串数组中找出两个不含有公共字母的单词,且这两个单词的长度乘积最大,如果不存在这样的两个单词,则返回0。
21 0
|
4月前
|
存储 Python
【Leetcode刷题Python】239. 滑动窗口最大值
文章介绍了两种解决LeetCode上"滑动窗口最大值"问题的方法:使用大堆树和双向递减队列,提供了详细的解析和Python代码实现。
37 0
|
3月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行