Performance
The main culprit for exceeding the time limit is here:
while end < s.count { ... }
A Swift string is a Collection
but not a RandomAccessCollection
, so that the complexity of var count
is \$ O(n) \$, where \$ n \$ is the number of characters in the string.
s.count
is called on each iteration, so that the total complexity becomes \$ O(n^2) \$.
Since you already converted the string to an array of characters, you can simply replace the condition by
while end < stringArray.count { ... }
Arrays are RandomAccessCollection
s and determining their count is a \$ O(1) \$ operation. That should already improve the performance considerably for large strings.
An alternative is to iterate over/keep track of string indices instead, that makes the stringArray
obsolete:
var start = s.startIndex // Start of current window var end = s.startIndex // End of current window var len = 0 // Length of current window while end != s.endIndex { let curChar = s[end] s.formIndex(after: &end) len += 1 // ... }
Some simplifications
Testing for an empty string can be done with isEmpty
:
if s.isEmpty || t.isEmpty || s.count < t.count { return "" }
When building the frequency map you can use the dictionary subscripting with default value:
var freq: [Character : Int] = [:] for curChar in t { freq[curChar, default: 0] += 1 }
and that can be further shorted using reduce(into:)
:
var freq = t.reduce(into: [:]) { dict, char in dict[char, default: 0] += 1 }
Use Optional
instead of magic values
Here
var resLen = Int.max
you are using a “magic value”: Int.max
indicates that no matching window has been found so far. That works because the given strings are unlikely to have \$ 2^{63} - 1 \$ characters, but it forces you to use exactly the same “magic value” at
if resLen == Int.max { ... }
Also the initial value of
var resStart = 0
is meaningless, it will be overwritten as soon as the first matching window is found.
Magic values are frowned upon in Swift because there is a dedicated language feature for the purpose: the optional values.
var resLen: Int?
clearly indicates an undefined value, and later be tested with optional binding.
A (minor) drawback is that you no longer simply compare
if resLen > end - start { ... }
but I'll come back to that later.
Use struct
s to combine related properties
Both the current and the best window are described by two properties (
var end = 0 var start = 0 var resStart = 0 var resLen = Int.max
or – with the above suggestion – by three properties
var start = s.startIndex // Start of current window var end = s.startIndex // End of current window var len = 0 // Length of current window // Similar for best window ...
With a
struct StringWindow { var startIndex: String.Index var endIndex: String.Index var length: Int // ... }
these related properties are nicely combined, and it makes the code more self-documenting:
var currentWindow = StringWindow(...) var bestWindow: StringWindow?
Comparing the length against the optional bestWindow
can be put into a method of that type, assigning a new best window is simply done with
bestWindow = currentWindow
and the final result can be determined with optional binding:
if let best = bestWindow { return String(s[best.startIndex..<best.endIndex]) } else { return "" }
Putting it together
Putting all the above suggestions together the code could look like this:
func minWindow(_ s: String, _ t: String) -> String { struct StringWindow { var startIndex: String.Index var endIndex: String.Index var length: Int init(for string: String) { self.startIndex = string.startIndex self.endIndex = string.startIndex self.length = 0 } func shorter(than other: StringWindow?) -> Bool { if let other = other { return length < other.length } else { return true } } } if s.isEmpty || t.isEmpty || s.count < t.count { return "" } var freq = t.reduce(into: [:]) { dict, char in dict[char, default: 0] += 1 } var distinct = freq.count var currentWindow = StringWindow(for: s) var bestWindow: StringWindow? while currentWindow.endIndex != s.endIndex { let curChar = s[currentWindow.endIndex] s.formIndex(after: ¤tWindow.endIndex) currentWindow.length += 1 if let val = freq[curChar] { freq[curChar] = val - 1 if val - 1 == 0 { distinct -= 1 } } while distinct == 0 { if currentWindow.shorter(than: bestWindow) { bestWindow = currentWindow } let curStart = s[currentWindow.startIndex] if let val = freq[curStart] { freq[curStart] = val + 1 if val + 1 > 0 { distinct += 1 } } s.formIndex(after: ¤tWindow.startIndex) currentWindow.length -= 1 } } if let best = bestWindow { return String(s[best.startIndex..<best.endIndex]) } else { return "" } }
Final remarks
Try to use more descriptive variable names (what is var distinct
) or at least document their meaning.
Don't abbreviate variable names: use e.g. length
instead of len
, or startIndex
instead of start
.
Determining the number of key/value entries in a dictionary can be simplified to
var distinct = freq.count
instead of freq.keys.count
.
The parentheses at
while (distinct == 0) { ... }
are not needed.
s.count < t.count
seems unjustified to me. IMHOs="ABCD"
andt="CBCBCBBCBBB"
is a valid input (the answer should be"BC"
).\$\endgroup\$