4
\$\begingroup\$

From LeetCode medium 3. Longest Substring Without Repeating Characters:

Given a string, find the length of the longest substring without repeating characters.

Examples:

Given "abcabcbb", the answer is "abc", which the length is 3.

Given "bbbbb", the answer is "b", with the length of 1.

Given "pwwkew", the answer is "wke", with the length of 3. Note that the answer must be a substring, "pwke" is a subsequence and not a substring.

Here is my solution, but the online judge told me "Time Limit Exceeded". Is there a better solution to this problem?

private extension String { subscript (index:Int) -> Character { return (self[self.startIndex.advancedBy(index)]) } } class Solution { func lengthOfLongestSubstring(s: String) -> Int { var str:String = "" var longest:Int = Int.min if s.isEmpty { return 0 } for i in 0...s.characters.count-1 { if !str.characters.contains(s[i]) { str += String(s[i]) } else { longest = max(longest, str.characters.count) str = "" } } return longest } } 
\$\endgroup\$

    2 Answers 2

    3
    \$\begingroup\$

    The Swift standard library does not have a built-in method to access the n'th character of a string. It is tempting to fill that gap with a custom extension like you did, but the problem is that

    self[self.startIndex.advancedBy(index)] 

    is a \$ O(index) \$ operation. In contrast to other languages where a "character" is a fixed-size object (and accessing the n'th character of a string a \$ O(1) \$ operation), a Swift Character represents a "Unicode grapheme cluster" which consists of one or more Unicode scalar values, see e.g. Strings in Swift 2 in the Swift blog.

    That makes

    for i in 0...s.characters.count-1 { // access `s[i]` with your custom subscript method } 

    a O(n^2) operation where n is the number of characters in the string. It is much more efficient to iterate over the characters with

    for c in s.characters { // do something with `c` } 

    More remarks:

    • The explicit type annotations in

      var str:String = "" var longest:Int = Int.min 

      are not necessary, the compiler can infer the type automatically.

    • If you start with var longest = 0 then the special case

      if s.isEmpty { return 0 } 

      becomes obsolete.

    • There are actually two errors in your code. If a repeating character is found then the new substring candidate consists of that character, i.e.

       longest = max(longest, str.characters.count) str = "" 

      should be

       longest = max(longest, str.characters.count) str = String(s[i]) 

      otherwise lengthOfLongestSubstring("aabcdd") returns 3 instead of 4.

      And the longest substring length must also be updated at the end of the string:

      longest = max(longest, str.characters.count) return longest 

      otherwise lengthOfLongestSubstring("aabcd") returns 1 instead of 4.

    Putting all that together, the method becomes

    func lengthOfLongestSubstring(s: String) -> Int { var str = "" var longest = 0 for c in s.characters { if !str.characters.contains(c) { str.append(c) } else { longest = max(longest, str.characters.count) str = String(c) } } longest = max(longest, str.characters.count) return longest } 

    which should be faster than your code.

    Another possible improvement is to store the characters of the current substring in an Array or a Set instead of a String. Which one is faster depends on the size of the strings, here is an example with an array:

    func lengthOfLongestSubstring(s: String) -> Int { var substringChars: [Character] = [] var longest = 0 for c in s.characters { if !substringChars.contains(c) { substringChars.append(c) } else { longest = max(longest, substringChars.count) substringChars = [c] } } longest = max(longest, substringChars.count) return longest } 

    As of Swift 4, a Swift string is a collection of its characters again, so for c in s.characters can be simplified to for c in s.

    \$\endgroup\$
    2
    • \$\begingroup\$note this won't work on a string like "dvdf"\$\endgroup\$
      – AndyRoid
      CommentedMar 20, 2022 at 20:23
    • \$\begingroup\$@AndyRoid: Yes, you are right. I reviewed the given code, but did not recognize that it has a flaw. Apparently that has been addressed in the other answer.\$\endgroup\$
      – Martin R
      CommentedMar 20, 2022 at 20:52
    1
    \$\begingroup\$

    The following code seems to work for me. The else of Martin didn't work for me. Ex. string "abacdef" gives me 5 as the length of the longest substring with the substring being "acdef". The longest substring in this case should be "bacdef" with length 6.

    func lengthOfLongestSubstring(s: String) -> Int { var str = "" var longest = 0 for c in s { if !str.contains(c) { str.append(c) } else { longest = max(longest, str.count) let index = str.index(str.startIndex, offsetBy: 1) if str[..<index] == String(c) { str = str.replacingOccurrences(of: String(c), with: "") } else { if let index = str.index(of: c) { let nextIndex = str.index(after: index) str = String(str[nextIndex...]) } } str.append(c) } } longest = max(longest, str.count) return longest } 
    \$\endgroup\$

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.