给定一个字符串,请你找出其中不含有重复字符的最长子串的长度.
示例 1:
输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例2:
输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例3:
输入: "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
解法一:暴力解决
思想:逐个检查所有的子字符串,看它是否不含有重复的字符。
算法:如果我们有一个函数allUnique(_ s: String, start: Int, end: Int) -> Bool,如果子字符串的字符都是唯一的,它会返回true,否则会返回false。我们遍历给定字符串s的所有可能的子字符串并调用函数allUnique,如果事实证明返回值为true,那么我们将会更新无重复子串的最大长度的答案。
为了枚举给定字符串的所有子字符串,我们需要枚举它们开始和结束的索引。假设开始和结束的索引分别为 i 和 j。
那么我们有0<=i<j<=n,我们使用i从0到n-1及j从i+1到n这两个嵌套的循环,枚举出所有子字符串。
要检查一个字符串是否有重复字符,可以使用集合。我们遍历字符串中的所有字符,并将它们逐个放入set中,在放置一个字符之前,检查改集合是否已经包含它,如果包含,我们会返回false,循环结束后,我们返回true。
let s: String = "abcabcbb" func lengthOfLongestSubstring(_ s: String) -> Int { var results: Int = 0 for i in 0..<s.count { for j in i+1..<s.count + 1 { if allUnique(s, start: i, end: j) { results = max(results, j - i)//更新最长子字符长度 } } } return results } func allUnique(_ s: String, start: Int, end: Int) -> Bool { var set: Set = Set<Character>() var characters = Array(s)//转为字符数组 for i in start..<end { let ch: Character = characters[i] if set.contains(ch) {return false} set.insert(ch) } return true } let result = lengthOfLongestSubstring(s) print(result)
解法二:滑动窗口法
暴力法非常简单,但它太慢了。那么我们该如何优化它呢?
在暴力法中,我们会反复检查一个子字符串是否含有有重复的字符,但这是没有必要的。
要检查一个字符是否已经在子字符串中,我们可以检查整个子字符串,这将产生一个复杂度为 O(n2) 的算法,但我们可以做得更好。
通过使用 HashSet 作为滑动窗口,我们可以用 O(1)O(1) 的时间来完成对字符是否在当前的子字符串中的检查。
let s: String = "abcabcbb" func lengthOfLongestSubstring(_ s: String) -> Int { var set: Set = Set<Character>() var characters = Array(s)//转为字符数组 var results: Int = 0 var i: Int = 0 var j: Int = 0 while i < s.count && j < s.count { if !set.contains(characters[j]) { set.insert(characters[j]) j += 1 results = max(results, j - i) } else { set.remove(characters[i]) i += 1 } } return results } let result = lengthOfLongestSubstring(s) print(result)
时间复杂度:O(2n)=O(n),在最糟糕的情况下,每个字符将被 i和 j 访问两次。
空间复杂度:O(min(m,n)),与之前的方法相同。滑动窗口法需要 O(k) 的空间,其中 k 表示 Set
的大小。而 Set 的大小取决于字符串 nn 的大小以及字符集 / 字母 mm 的大小。
以上就是两种方式,大家要着重理解一下后一种方式!!!