Minimum Window Substring in Swift exceeds LeetCode runtime benchmark - swift

This is a popular question on LeetCode:
Given two strings s and t of lengths m and n respectively,
return the minimum window substring of s such that every character in t (including duplicates)
is included in the window. If there is no such substring, return the empty string "".
The testcases will be generated such that the answer is unique.
A substring is a contiguous sequence of characters within the string.
Example:
Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"
Explanation: The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t.
I converted the java solution provided by LeetCode to Swift since this is the language I am practicing in. Here is my code below:
func minWindowSlidingWindow(_ s: String, _ t: String) -> String
{
if s == t
{
return s
}
var uniqueCharacterHashTable: [Character: Int] = [:]
for character in t
{
if let countOfChar = uniqueCharacterHashTable[character]
{
uniqueCharacterHashTable[character] = countOfChar + 1
continue
}
uniqueCharacterHashTable[character] = 1
}
let uniqueCharactersRequired = uniqueCharacterHashTable.keys.count
var uniqueCharactersFormed = 0
var currentWindowCharacterHashTable: [Character: Int] = [:]
var minSequenceSize = Int.max
var minimumSequenceStart = 0
var minimumSequenceEnd = 0
var currentStartIndexInt = 0
var currentEndIndexInt = 0
while currentEndIndexInt < s.count
{
let endIndex = s.index(s.startIndex, offsetBy: currentEndIndexInt)
var currentCharacter = s[endIndex]
if var characterCount = currentWindowCharacterHashTable[currentCharacter]
{
characterCount += 1
currentWindowCharacterHashTable[currentCharacter] = characterCount
}
else
{
currentWindowCharacterHashTable[currentCharacter] = 1
}
if let _ = uniqueCharacterHashTable[currentCharacter],
currentWindowCharacterHashTable[currentCharacter] == uniqueCharacterHashTable[currentCharacter]
{
uniqueCharactersFormed += 1
}
while currentStartIndexInt <= currentEndIndexInt && uniqueCharactersFormed == uniqueCharactersRequired
{
let startIndex = s.index(s.startIndex, offsetBy: currentStartIndexInt)
currentCharacter = s[startIndex]
if minSequenceSize == Int.max || currentEndIndexInt - currentStartIndexInt + 1 < minSequenceSize
{
minSequenceSize = currentEndIndexInt - currentStartIndexInt + 1
minimumSequenceStart = currentStartIndexInt
minimumSequenceEnd = currentEndIndexInt
}
if let characterCountInWindow = currentWindowCharacterHashTable[currentCharacter]
{
currentWindowCharacterHashTable[currentCharacter] = characterCountInWindow - 1
}
if let _ = uniqueCharacterHashTable[currentCharacter],
let currentCharOriginalCount = uniqueCharacterHashTable[currentCharacter],
let charInWindowCount = currentWindowCharacterHashTable[currentCharacter],
currentCharOriginalCount > charInWindowCount
{
uniqueCharactersFormed -= 1
}
currentStartIndexInt += 1
}
currentEndIndexInt += 1
}
if minSequenceSize == Int.max
{
return ""
}
let startIndex = s.index(s.startIndex, offsetBy: minimumSequenceStart)
let endIndex = s.index(s.startIndex, offsetBy: minimumSequenceEnd)
return String(s[startIndex ... endIndex])
}
This works for the basic test cases but as the string size gets huge like 100,000 for example - it gets super slow even though I use the same data structures (I think) as suggested in the Java solution.
Can anyone point me as to where the bottleneck in this code lies and how could I optimize this further.

Related

Karatsuba multiplication in swift

I was trying to implement Karatsuba multiplication in swift. I wrote the below code and it is working fine for some smaller numbers but as the number gets bigger this code fails to give the correct answer. I have debugged in every possible way I can but could not find the bug. Algorithm wise I think I did correctly write the code. And the code is working fine for smaller numbers. But the final answer is wrong for bigger numbers. If anyone out there can crack down the mistake I'm making, pls do help me
func findMultiplication(x: String, y: String) -> String {
if isZero(str: x) || isZero(str: y) {
return "0"
}
var x = removeLeadingZeros(number: x)
var y = removeLeadingZeros(number: y)
if x.count < 2 || y.count < 2 {
let result = Int(x)!*Int(y)!
return String(result)
}
var middleIndexX: String.Index
var middleIndexY: String.Index
var middleIndex: Int
if x.count >= y.count {
y = addLeftPaddingZeros(numberOfZeros: x.count-y.count, for: y)
middleIndex = x.count / 2
if x.count % 2 != 0 {
middleIndex += 1
}
} else {
x = addLeftPaddingZeros(numberOfZeros: y.count-x.count, for: x)
middleIndex = y.count / 2
if y.count % 2 != 0 {
middleIndex += 1
}
}
middleIndexX = x.index(x.startIndex, offsetBy: middleIndex)
middleIndexY = y.index(y.startIndex, offsetBy: middleIndex)
let a = String(x[x.startIndex..<middleIndexX])
let b = String(x[middleIndexX..<x.endIndex])
let c = String(y[y.startIndex..<middleIndexY])
let d = String(y[middleIndexY..<y.endIndex])
let ac = findMultiplication(x: a, y: c)
let bd = findMultiplication(x: b, y: d)
let aPb = Int(a)! + Int(b)!
let cPd = Int(c)! + Int(d)!
let gauss = findMultiplication(x: String(aPb), y: String(cPd))
let thirdItem = String(Int(gauss)! - Int(ac)! - Int(bd)!)
var returnSum = 0
returnSum += Int(addLeftPaddingZeros(numberOfZeros: x.count, for: ac, isLeft: false)) ?? 0
returnSum += Int(addLeftPaddingZeros(numberOfZeros: middleIndex, for: thirdItem, isLeft: false)) ?? 0
returnSum += Int(bd) ?? 0
return String(returnSum)
}
print(findMultiplication(x: "123400", y: "123711"))
func removeLeadingZeros(number: String) -> String {
var number = number
while number.first == "0" {
number.removeFirst()
}
if number == "" {
return "0"
}
return number
}
//The function name is given like this only. BUt his will help to add padding zero in left and right also
func addLeftPaddingZeros(numberOfZeros: Int, for str: String, isLeft: Bool = true) -> String {
var padding = ""
for _ in 0 ..< numberOfZeros {
padding += "0"
}
if isLeft {
return padding+str
} else {
return str + padding
}
}
func isZero(str: String) -> Bool {
for char in str {
if char != "0" {
return false
}
}
return true
}

Task from the interview. How we would solve it?

Convert String in this way
let initialString = "atttbcdddd"
// result must be like this "at3bcd4"
But repetition must be more than 2. For example, if we have "aa" the result will be "aa", but if we have "aaa", the result will be "a3"
One more example:
let str = "aahhhgggg"
//result "aah3g4"
My try:
func encrypt(_ str: String) -> String {
let char = str.components(separatedBy: "t") //must input the character
var count = char.count - 1
var string = ""
string.append("t\(count)")
return string
}
if i input "ttttt" it will return "t5" but i should input the character
What you are looking for is the “Run-length encoding”. Note that this is not an encryption!
Here is a possible implementation (explanations inline):
func runLengthEncode(_ str: String) -> String {
var result = ""
var pos = str.startIndex // Start index of current run
while pos != str.endIndex {
let char = str[pos]
// Find index of next run (or `endIndex` if there is none):
let next = str[pos...].firstIndex(where: { $0 != char }) ?? str.endIndex
// Compute the length of the current run:
let length = str.distance(from: pos, to: next)
// Append compressed output to the result:
result.append(length <= 2 ? String(repeating: char, count: length) : "\(char)\(length)")
pos = next // ... and continue with next run
}
return result
}
Examples:
print(runLengthEncode("atttbcdddd")) // at3bcd4
print(runLengthEncode("aahhhgggg")) // aah3g4
print(runLengthEncode("abbbaaa")) // ab3a3
Checkout this :
func convertString(_ input : String) -> String {
let allElements = Array(input)
let uniqueElements = Array(NSOrderedSet(array: allElements)) as! [Character]
var outputString = ""
for uniqueChar in uniqueElements {
var count = 0
for char in allElements {
if char == uniqueChar {
count+=1
}
}
if count > 2 {
outputString += "\(uniqueChar)\(count)"
} else if count == 2 {
outputString += "\(uniqueChar)\(uniqueChar)"
} else {
outputString += "\(uniqueChar)"
}
}
return outputString
}
Input : convertString("atttbcdddd")
Output : at3bcd4
I've tried it before for one of the interview and also I think you too :). However, very simple way to do it is just go through step by step of code.
let initialString = "atttbcdddd"
var previousChar: Character = " "
var output = ""
var i = 1 // Used to count the repeated charaters
var counter = 0 // To check the last character has been reached
//Going through each character
for char in initialString {
//Increase the characters counter to check the last element has been reached. If it is, add the character to output.
counter += 1
if previousChar == char { i += 1 }
else {
output = output + (i == 1 ? "\(previousChar)" : "\(previousChar)\(i)")
i = 1
}
if initialString.count == counter {
output = output + (i == 1 ? "\(previousChar)" : "\(previousChar)\(i)")
}
previousChar = char
}
let finalOutput = output.trimmingCharacters(in: .whitespacesAndNewlines)
print(finalOutput)
let initialString = "atttbcdddd"
let myInitialString = initialString + " "
var currentLetter: Character = " "
var currentCount = 1
var answer = ""
for (_, char) in myInitialString.enumerated(){
if char == currentLetter {
currentCount += 1
} else {
if currentCount > 1 {
answer += String(currentCount)
}
answer += String(char)
currentCount = 1
currentLetter = char
}
}
print(answer)
Use reduce here.
func exp(_ s : String, _ term: String) -> String{ //term_inator: Any Char not in the Sequence.
guard let first = s.first else {return ""}
return """
\(s.dropFirst().appending(term).reduce(("\(first)",1)){ r, c in
let t = c == r.0.last!
let tc = t ? r.1 : 0
let tb = t ? "" : "\(c)"
let ta = t ? "" : r.1 > 2 ? "\(r.1)" : r.1 == 2 ? "\(r.0.last!)" : ""
return (r.0 + ta + tb, tc + 1)
}.0.dropLast())
"""}
print(exp(initialString, " "))
let initialString = "abbbaaa" // ab3a3
let initialString = "aahhhgggg" // aah3g4
let initialString = "aabbaa" // aabbaa

Need to find consecutive sequence like "6789" or "abcd" in Swift

I need help to find consecutive sequence for example more than 3 characters in ascending order. I've already implemented one solution but It's not universal.
Examples what should be found - "1234", "abcd", "5678".
And what shouldn't be found - "123", "adced", "123abc", "89:;"
Particularly the case "89:;", symbol ":" - is 58 in uniCode and "9" - is 57, that's why my approach does not work in the case.
Implementation should be in swift.
Additional clarification
For now it would be enough to find the sequences only in English letters and numbers.
private func findSequence(sequenceLength: Int, in string: String) -> Bool {
let scalars = string.unicodeScalars
var unicodeArray: [Int] = scalars.map({ Int($0.value) })
var currentLength: Int = 1
var i = 0
for number in unicodeArray {
if i+1 >= unicodeArray.count {
break
}
let nextNumber = unicodeArray[i+1]
if number+1 == nextNumber {
currentLength += 1
} else {
currentLength = 1
}
if currentLength >= sequenceLength {
return true
}
i += 1
}
return false
}
var data = [1,2,5,4,56,6,7,9,6,5,4,5,1,2,5,4,56,6,7,9,8,1,1,2,5,4,56,6,7,9,8,1,1,2,5,4,56,6,7,9,8,1,1,2,5,4,56,6,7,9,8,1,1,2,5,4,56,6,7,9,8,11,2,5,4,56,6,7,9,8,1,2,3]
for i in 0...data.count{
if i+2 < data.count{
if Int(data[i] + data[i+2]) / 2 == data[i+1] && Int(data[i] + data[i+2]) % data[i+1] == 0 && data[i+1] != 1 && data[i] < data[i+1]{
print(data[i] ,data[i+1], data[i+2])
}
}
}
You can check for sequence with CharacterSet
func findSequence(sequenceLength: Int, in string: String) -> Bool {
// It would be better to extract this out of func
let digits = CharacterSet.decimalDigits
let lowercase = CharacterSet(charactersIn: "a"..."z")
let uppercase = CharacterSet(charactersIn: "A"..."Z")
let controlSet = digits.union(lowercase).union(uppercase)
// ---
let scalars = string.unicodeScalars
let unicodeArray = scalars.map({ $0 })
var currentLength: Int = 1
var i = 0
for number in unicodeArray where controlSet.contains(number) {
if i+1 >= unicodeArray.count {
break
}
let nextNumber = unicodeArray[i+1]
if UnicodeScalar(number.value+1) == nextNumber {
currentLength += 1
} else {
currentLength = 1
}
if currentLength >= sequenceLength {
return true
}
i += 1
}
return false
}
I did assumed that "a" ... "z" and "A"..."Z" are consecutive here, to make it in range, but it may be better do explicitly list all the symbols you want.
Or use CharacterSet.alphanumerics, but is not limited to basic latin alphabet.

swift string advancedBy is slow?

So I am writing in swift to practice some online judge.
Here's the issue: Longest Palindromic Substring
Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
So I am using dp to solve it in swift:
class Solution {
func longestPalindrome(s: String) -> String {
var hash = Array(count: s.characters.count, repeatedValue: Array(count: s.characters.count, repeatedValue: false))
for i in 0 ..< s.characters.count {
hash[i][i] = true
}
var maxStart = 0
var maxEnd = 0
var maxCount = 1
for i in 1.stride(through: s.characters.count - 1, by: 1) {
for j in 0 ..< s.characters.count - 1 {
if j + i < s.characters.count {
if isValidPalindrome(j, j + i, s, hash) {
hash[j][j + i] = true
if maxCount < i + 1 {
maxCount = i
maxStart = j
maxEnd = j + i
}
}
}
else {
break
}
}
}
// construct max palindrome string, swift string is so dummy
var str = ""
for i in maxStart...maxEnd {
let index = s.characters.startIndex.advancedBy(i)
str += String(s.characters[index])
}
return str
}
func isValidPalindrome(start: Int, _ end: Int, _ s: String, _ hash: [[Bool]]) -> Bool {
// end <= s's length - 1
let startIndex = s.startIndex.advancedBy(start)
let endIdnex = s.startIndex.advancedBy(end)
if end - start == 1 {
return s[startIndex] == s[endIdnex]
}
else {
let left = start + 1
let right = end - 1
return s[startIndex] == s[endIdnex] && hash[left][right]
}
}
}
I am thinking it's a correct one, but when I submit, always time exceeded for long strings like:
"kyyrjtdplseovzwjkykrjwhxquwxsfsorjiumvxjhjmgeueafubtonhlerrgsgohfosqssmizcuqryqomsipovhhodpfyudtusjhonlqabhxfahfcjqxyckycstcqwxvicwkjeuboerkmjshfgiglceycmycadpnvoeaurqatesivajoqdilynbcihnidbizwkuaoegmytopzdmvvoewvhebqzskseeubnretjgnmyjwwgcooytfojeuzcuyhsznbcaiqpwcyusyyywqmmvqzvvceylnuwcbxybhqpvjumzomnabrjgcfaabqmiotlfojnyuolostmtacbwmwlqdfkbfikusuqtupdwdrjwqmuudbcvtpieiwteqbeyfyqejglmxofdjksqmzeugwvuniaxdrunyunnqpbnfbgqemvamaxuhjbyzqmhalrprhnindrkbopwbwsjeqrmyqipnqvjqzpjalqyfvaavyhytetllzupxjwozdfpmjhjlrnitnjgapzrakcqahaqetwllaaiadalmxgvpawqpgecojxfvcgxsbrldktufdrogkogbltcezflyctklpqrjymqzyzmtlssnavzcquytcskcnjzzrytsvawkavzboncxlhqfiofuohehaygxidxsofhmhzygklliovnwqbwwiiyarxtoihvjkdrzqsnmhdtdlpckuayhtfyirnhkrhbrwkdymjrjklonyggqnxhfvtkqxoicakzsxmgczpwhpkzcntkcwhkdkxvfnjbvjjoumczjyvdgkfukfuldolqnauvoyhoheoqvpwoisniv"
I can get the correct result qahaq after some time, and I am wondering why it's so slow. If I write it in other language, not so bad.
I suspect the API s.startIndex.advancedBy(start) is causing it, but I checked the doc, no time complexity and no other ways to turn an int to the startIndex type?
Any ideas to replace advancedBy? Thank in advance.
For those having the same issue: I turned swift String into Array, and it gets much faster.
I also looked into Swift source code about the advancedBy implementation, it's a O(n) opreation, that's why it's slow.
For whom is interested in the implementation, take a look at https://github.com/apple/swift/blob/8e12008d2b34a605f8766310f53d5668f3d50955/stdlib/public/core/Index.swift
You will see advancedBy is merely multiple successor():
#warn_unused_result
public func advanced(by n: Distance) -> Self {
return self._advanceForward(n)
}
/// Do not use this method directly; call advanced(by: n) instead.
#_transparent
#warn_unused_result
internal func _advanceForward(_ n: Distance) -> Self {
_precondition(n >= 0,
"Only BidirectionalIndex can be advanced by a negative amount")
var p = self
var i : Distance = 0
while i != n {
p._successorInPlace()
i += 1
}
return p
}
This should do the trick. Before implementing it, I recommend checking out some explanations such as this guy's. https://www.youtube.com/watch?v=obBdxeCx_Qs. I'm not affiliated with him, though I do believe his video is somewhat useful.
func longestPalindrome(_ s: String) -> String {
var charArray = [Character("$"), Character("#")]
for i in s.characters {
charArray += [i, Character("#")]
}
charArray += [Character("#")]
var mir = 0, c = 0, r = 0, longestPalindromeIndex = 0, longestPalindromeLength = 0, ss = "", returnString = ""
var p = [Int]()
//MARK: For loop
for i in 0...(charArray.count - 1) {
p += [0, 0]
mir = 2 * c - i
if i < r {
p[i] = min(r - i, p[mir])
}
if i - (1 + p[i]) >= 0 && i + (1 + p[i]) < charArray.count - 1 {
while charArray[i + (1 + p[i])] == charArray[i - (1 + p[i])] {
p[i] += 1
}
}
if p[i] > longestPalindromeLength {
longestPalindromeIndex = i
longestPalindromeLength = p[i]
}
if i + p[i] > r {
c = i
r = i + p[i]
}
}//for loop
for i in Array(charArray[(longestPalindromeIndex - longestPalindromeLength)...(longestPalindromeIndex + longestPalindromeLength)]) {
ss = String(i)
if ss != "#" && ss != "$" && ss != "#" {
returnString += ss
}
}
return returnString
}//func

What's the best way to cut Swift string into 2-letter-strings?

I need to split a string into 2-letter pieces. Like “friend" -> "fr" "ie" "nd". (Okay, its a step for me to change HEX string to Uint8 Array)
My code is
for i=0; i<chars.count/2; i++ {
let str = input[input.startIndex.advancedBy(i*2)..<input.startIndex.advancedBy(i*2+1)]
bytes.append(UInt8(str,radix: 16)!)
}
But I don't know why I cannot use Range to do this split. And I have no idea what will happen when i*2+1 is bigger than string's length. So what's the best way to cut Swift string into 2-letter-strings?
Your range wasn't working because you need to use ... instead of ..<.
let input = "ff103"
var bytes = [UInt8]()
let strlen = input.characters.count
for i in 0 ..< (strlen + 1)/2 {
let str = input[input.startIndex.advancedBy(i*2)...input.startIndex.advancedBy(min(strlen - 1, i*2+1))]
bytes.append(UInt8(str,radix: 16) ?? 0)
}
print(bytes) // [255, 16, 3]
Here is another take on splitting the string into 2-letter strings. advancedBy() is an expensive O(n) operation, so this version keeps track of start and just marches it ahead by 2 each loop, and end is based on start:
let input = "friends"
var strings = [String]()
let strlen = input.characters.count
var start = input.startIndex
let lastIndex = strlen > 0 ? input.endIndex.predecessor() : input.startIndex
for i in 0 ..< (strlen + 1)/2 {
start = i > 0 ? start.advancedBy(2) : start
let end = start < lastIndex ? start.successor() : start
let str = input[start...end]
strings.append(str)
}
print(strings) // ["fr", "ie", "nd", "s"]
Alternate Answer:
Using ranges is probably overkill. It is easy just to add the characters to an array and make Strings from those:
let input = "friends"
var strings = [String]()
var newchars = [Character]()
for c in input.characters {
newchars.append(c)
if newchars.count == 2 {
strings.append(String(newchars))
newchars = []
}
}
if newchars.count > 0 {
strings.append(String(newchars))
}
print(strings) // ["fr", "ie", "nd", "s"]
And here is the new version for making [UInt8]:
let input = "ff103"
var bytes = [UInt8]()
var newchars = [Character]()
for c in input.characters {
newchars.append(c)
if newchars.count == 2 {
bytes.append(UInt8(String(newchars), radix: 16) ?? 0)
newchars = []
}
}
if newchars.count > 0 {
bytes.append(UInt8(String(newchars), radix: 16) ?? 0)
}
print(bytes) // [255, 16, 3]
Based on #LeoDabus' answer, we can make an extension with a method that will return substrings of any length, and a computed property that returns [UInt8]:
extension String {
func substringsOfLength(length: Int) -> [String] {
if length < 1 { return [] }
var result:[String] = []
let chars = Array(characters)
for index in 0.stride(to: chars.count, by: length) {
result.append(String(chars[index ..< min(index+length, chars.count)]))
}
return result
}
var toUInt8: [UInt8] {
var result:[UInt8] = []
let chars = Array(characters)
for index in 0.stride(to: chars.count, by: 2) {
let str = String(chars[index ..< min(index+2, chars.count)])
result.append(UInt8(str, radix: 16) ?? 0)
}
return result
}
}
let input = "friends"
let str2 = input.substringsOfLength(2) // ["fr", "ie", "nd", "s"]
let str0 = input.substringsOfLength(0) // []
let str3 = input.substringsOfLength(3) // ["fri", "end", "s"]
let bytes = "ff107".toUInt8 // [255, 16, 7]
Another option just for fun:
extension String {
var pairs:[String] {
var result:[String] = []
let chars = Array(characters)
for index in 0.stride(to: chars.count, by: 2) {
result.append(String(chars[index..<min(index+2, chars.count)]))
}
return result
}
}
let input = "friends"
let pairs = input.pairs
print(pairs) // ["fr", "ie", "nd", "s"]