LeetCode - #30 串联所有单词的子串(Top 100)

98 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情

前言

本题为 LeetCode 前 100 高频题

本题由于没有合适答案为以往遗留问题,最近有时间将以往遗留问题一一完善。

我们社区陆续会将顾毅(Netflix 增长黑客,《iOS 面试之道》作者,ACE 职业健身教练。)的 Swift 算法题题解整理为文字版以方便大家学习与阅读。

LeetCode 算法到目前我们已经更新到 29 期,我们会保持更新时间和进度(周一、周三、周五早上 9:00 发布),每期的内容不多,我们希望大家可以在上班路上阅读,长久积累会有很大提升。

不积跬步,无以至千里;不积小流,无以成江海,Swift社区 伴你前行。如果大家有建议和意见欢迎在文末留言,我们会尽力满足大家的需求。

难度水平:困难

1. 描述

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

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

例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd""cdabef", "cdefab""efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。 返回所有串联字串在 s 中的开始索引。你可以以 任意顺序 返回答案。

2. 示例

示例 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 <= 10^4
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • words[i] 和 s 由小写英文字母组成

3. 答案

func findSubstring(_ s: String, _ words: [String]) -> [Int] {

  if words.count == 0 {
      return []
  }
  let perWord = words.first!.count

  if s.count < perWord * words.count {
      return []
  }
  //找出所有words里面的单词作为key,个数作为value,存到字典里面
  var wordsDic = [String: Int]()
  for word in words {

      let num = wordsDic[word]
      wordsDic[word] = (num ?? 0) + 1
  }

  let chars = Array(s)
  //存放结果的数组
  var result = [Int]()
  //遍历s字符串,创建新字典currentWordsDic存单词:个数。比较currentWordsDic和wordsDic个数
  var i = 0
  while i <= chars.count - perWord * words.count && i < perWord {

      var currentWordsDic = [String: Array<Int>]()//key:单词,value:数组index

      var startIndex = i
      var curIndex = startIndex

      while startIndex <= s.count - perWord * words.count {

          var curStr = ""
          for k in curIndex..<curIndex+perWord {
              curStr += String(chars[k])
          }

          if (wordsDic[curStr] ?? 0) == 0 {
              //如果这个值不在原数组中
              curIndex += perWord
              startIndex = curIndex
              currentWordsDic = [String: Array<Int>]()//key:单词,value:数组index
          }else {

              if (currentWordsDic[curStr] ?? []).count == 0 {
                  currentWordsDic[curStr] = [curIndex]
              }else {
                  currentWordsDic[curStr]!.append(curIndex)
              }

              if wordsDic[curStr]! - (currentWordsDic[curStr] ?? []).count < 0 {
                  //如果这个值数量已经超出原数组,找出这个值的第一个index,并删除字典里面此index之前的数据,startIndex = 此index+3
                  let array = currentWordsDic[curStr]!
                  let index = array.first!
                  if index - startIndex < curIndex - (index + perWord) {

                      for j in startIndex...index {
                          var string = ""
                          for k in j..<j+perWord {
                              string += String(chars[k])
                          }
                          currentWordsDic[string]!.removeFirst()
                      }
                  }else {

                      currentWordsDic = [String: Array<Int>]()//key:单词,value:数组index
                      for j in (index + perWord)...curIndex {
                          var string = ""
                          for k in j..<j+perWord {
                              string += String(chars[k])
                          }
                          if (currentWordsDic[string] ?? []).count == 0 {
                              currentWordsDic[string] = [j]
                          }else {
                              currentWordsDic[string]!.append(j)
                          }

                      }
                  }
                  startIndex = index + perWord
                  curIndex += perWord
              }else {
                  if curIndex + perWord - startIndex == perWord * words.count {
                      //如果遍历完数组,即找到了与数组单词适合的p子串
                      result.append(startIndex)

                      var stringStart = ""

                      for k in startIndex..<startIndex+perWord {
                          stringStart += String(chars[k])
                      }
                      currentWordsDic[stringStart]?.removeFirst()
                      curIndex += perWord
                      startIndex += perWord
                  }else {

                      curIndex += perWord
                  }
              }

          }

      }

      i += 1
  }

  return result
}

点击前往 LeetCode 练习

关于我们

我们是由 Swift 爱好者共同维护,我们会分享以 Swift 实战、SwiftUI、Swift 基础为核心的技术内容,也整理收集优秀的学习资料。