76. Minimum Window Substring in Swift

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 1:

Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"
Explanation: The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t.

Example 2:

Input: s = "a", t = "a"
Output: "a"
Explanation: The entire string s is the minimum window.

Example 3:

Input: s = "a", t = "aa"
Output: ""
Explanation: Both 'a's from t must be included in the window.
Since the largest window of s only has one 'a', return empty string.

Constraints:

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 105
  • s and t consist of uppercase and lowercase English letters.

The logic is explained here https://www.youtube.com/watch?v=eS6PZLjoaq8

class SolutionString {
	func helperMinLengthOfRearrangedSubstring(s: String, containing substring: String) -> (Int, [Int]) {
		// lets take two dictionaries to store the count of occurences of the substring
		// set the values in the have dictionary to the number of occurences
		// e.g. abc will have ["a":1, "b":1, c:"1"]
		let haveDict = getDictWithOriginalCountOfCharactersInSubstring(t: substring)
		// initialize the need dictionary with zero values
		// e.g. abc will have ["a":0, "b":0, c:"0"]
		var needDict = initDictWithZeroValues(t: substring)
		// lets take two pointers
		var leftPointer = 0
		var rightPointer = 0
		// convert the string to an array of characters
		let stringArray = Array(s)
		// set the minimum
		var minimum = Int.max
		var boundaries: [Int] = [Int]()
		//print(stringArray)
		// always make sure, the left pointer is less than right pointer
		// and right pointer is always less than the max count
		while leftPointer <= rightPointer && rightPointer < stringArray.count {
			// print(leftPointer, rightPointer, stringArray[rightPointer])
			// check if this character is present in the substring
			if needDict.keys.contains(stringArray[rightPointer]) {
				// this character is present, lets increment the count
				// increment value in set
				needDict[stringArray[rightPointer]]! += 1
				//print("contains \(stringArray[rightPointer]) \(haveDict) \(needDict)")
				// check if we have all the characters in the need dictionary
				// if they are the same, then lets get the minimum
				if areDictsTheSame(have: haveDict, need: needDict) {
					// print("they are the same \(leftPointer) \(rightPointer) \(needDict)")
					if (rightPointer - leftPointer) < minimum {
						minimum = rightPointer - leftPointer
						boundaries = [leftPointer, rightPointer]
						//print("minimum is \(minimum) \(boundaries)")
					}
					//print("leftPointe \(leftPointer) right \(rightPointer)")
					// we will try to reduce the minimum by moving the left pointer
					// lets start moving the left pointer and initialize the zero dictionary
					var zeroDict = initDictWithZeroValues(t: substring)
					for j in leftPointer...rightPointer {
						//
						if zeroDict[stringArray[j]] != nil {
							// now val is not nil and the Optional has been unwrapped, so use it
							zeroDict[stringArray[j]]! += 1
						}
					}
					var newZeroDict = zeroDict
					while areDictsTheSame(have: haveDict, need: newZeroDict) {
						//print("they are the same inside \(zeroDict) \(leftPointer) \(rightPointer)")
						// we can remove one element from the stringArray
						// increase left pointer
						// since we remove this element from the string, lets set
						// this value to zero in the needDict
						// e.g. ADOBEBANC substring=ABC
						// left is at A, it satisfies the constraint that it has
						// all the characters in the substring
						// let's move the left one place
						if needDict.keys.contains(stringArray[leftPointer]) {
							needDict[stringArray[leftPointer], default: 0] -= 1
						}
						// find the minimum
						if (rightPointer - leftPointer) < minimum {
							minimum = rightPointer - leftPointer
							boundaries = [leftPointer, rightPointer]
							//print("minimum is \(minimum) \(boundaries)")
						}
						// increment left pointer
						leftPointer += 1
						// reset the zeroDict dictionary to zero again
						newZeroDict = initDictWithZeroValues(t: substring)
						// print("after zero init \(zeroDict) \(zeroDict.keys)")
						// lets populate the zeroDict array again with the number of
						// values present in this range
						// e.g. DOBEB then we will have "d":1 "o":1 "b":2 "e":2
						if leftPointer <= rightPointer {
							for j in leftPointer...rightPointer {
								if zeroDict[stringArray[j]] != nil {
									// now val is not nil and the Optional has been unwrapped, so use it
									//print("second loop",val, stringArray[j])
									newZeroDict[stringArray[j]]! += 1
								}
							}
						}
					}
				}
			}
			// increment right pointer
			rightPointer += 1
		}
		//print("return \(minimum) \(boundaries)")
		if boundaries.count == 0 {
			return (0, boundaries)
		}
		return (minimum+1, boundaries)
	}
	
	func minWindow(_ s: String, _ t: String) -> String {
		// check the constraints
		if Int(t.count) > Int(pow(10.0, 5.0)) {
			return ""
		}
		if s.count < t.count {
			return ""
		}
		if s.count == 1 && t.count == 1 {
			if s == t {
				return s
			} else {
				return ""
			}
		}
		let (_, boundaries) = helperMinLengthOfRearrangedSubstring(s: s, containing: t)
		var returnString = ""
		if boundaries.count >= 2 && boundaries[0] <= boundaries[1] {
			returnString = String(s[s.index(s.startIndex, offsetBy: boundaries[0]) ... s.index(s.startIndex, offsetBy: boundaries[1])])
		}
		return returnString
	}
	
	func areDictsTheSame(have: [Character: Int], need: [Character: Int]) -> Bool {
		for i in have.keys {
			if need[i]! < have[i]! {
				return false
			}
		}
		return true
	}
	
	func initDictWithZeroValues(t: String) -> [Character: Int] {
		var dict = [Character:Int]()
		for char in t {
			dict[char] = 0
		}
		return dict
	}
	
	func getDictWithOriginalCountOfCharactersInSubstring(t: String) -> [Character: Int] {
		var dict = [Character:Int]()
		for char in t {
			if dict[char] != nil {
				dict[char, default: 0] += 1
			} else {
				dict[char] = 1
			}
		}
		return dict
	}
}

let solutionString = SolutionString()
print(solutionString.minWindow("ADOBECODEBANC", "ABC"))
//print("Solution ", solutionString.minWindow("ab", "a"))
//print("Solution ", solutionString.minWindow("ab", "A"))
//print(solutionString.minWindow("a", "taa"))
//print(solutionString.minWindow("a", "a"))

Leave a Reply

Your email address will not be published. Required fields are marked *