diff --git a/Array/BombEnemy.swift b/Array/BombEnemy.swift new file mode 100644 index 00000000..98234e7d --- /dev/null +++ b/Array/BombEnemy.swift @@ -0,0 +1,50 @@ +/** + * Question Link: https://leetcode.com/problems/bomb-enemy/ + * Primary idea: Greedy. Update the result only when there is wall or at the beginning. + * Time Complexity: O(mn), Space Complexity: O(n) + * + */ + +class BombEnemy { + func maxKilledEnemies(_ grid: [[Character]]) -> Int { + let m = grid.count, n = grid[0].count + var res = 0, rowHit = 0, colHit = Array(repeating: 0, count: n) + + for i in 0.. String { + let num = Array(num) + var firstHalf = num[0.. Bool { + var violateIdx = -1 + + for i in (1.. nums[i - 1] { + violateIdx = i - 1 + break + } + } + + if violateIdx == -1 { + nums.reverse() + return false + } + + for i in ((violateIdx + 1).. nums[violateIdx] { + swap(&nums, i, violateIdx) + break + } + } + + nums[(violateIdx + 1)...].reverse() + return true + } + + private func swap(_ nums: inout [Int], _ l: Int, _ r: Int) { + (nums[l], nums[r]) = (nums[r], nums[l]) + } +} diff --git a/Array/NextPermutation.swift b/Array/NextPermutation.swift index 332f721a..4a76991e 100644 --- a/Array/NextPermutation.swift +++ b/Array/NextPermutation.swift @@ -9,36 +9,36 @@ class NextPermutation { func nextPermutation(_ nums: inout [Int]) { - guard let violateIndex = findViolate(nums) else { + guard let violateIdx = findViolate(nums) else { nums.reverse() return } - - swap(&nums, violateIndex, findLeastGreater(nums, violateIndex)) - nums = nums[0...violateIndex] + nums[(violateIndex + 1)...].reversed() + + swap(&nums, findFirstGreater(nums, violateIdx), violateIdx) + nums[(violateIdx + 1)...].reverse() + } + + private func findFirstGreater(_ nums: [Int], _ violateIdx: Int) -> Int { + for i in ((violateIdx + 1).. nums[violateIdx] { + return i + } + } + + return -1 } - - fileprivate func findViolate(_ nums: [Int]) -> Int? { + + private func findViolate(_ nums: [Int]) -> Int? { for i in (1.. nums[i - 1] { return i - 1 } } - + return nil } - - fileprivate func findLeastGreater(_ nums: [Int], _ violateIndex: Int) -> Int { - for i in (violateIndex + 1.. nums[violateIndex] { - return i - } - } - - fatalError() - } - - fileprivate func swap(_ nums: inout [T], _ indexL: Int, _ indexR: Int) { - (nums[indexL], nums[indexR]) = (nums[indexR], nums[indexL]) + + private func swap(_ nums: inout [Int], _ l: Int, _ r: Int) { + (nums[l], nums[r]) = (nums[r], nums[l]) } -} \ No newline at end of file +} diff --git a/Array/SquaresSortedArray.swift b/Array/SquaresSortedArray.swift new file mode 100644 index 00000000..e1f4b31b --- /dev/null +++ b/Array/SquaresSortedArray.swift @@ -0,0 +1,29 @@ +/** + * Question Link: https://leetcode.com/problems/squares-of-a-sorted-array/ + * Primary idea: Two pointers. Compare absolute value and assign the bigger one to the right most index of the result. + * Time Complexity: O(n), Space Complexity: O(1) + * + */ + +class SquaresSortedArray { + func sortedSquares(_ nums: [Int]) -> [Int] { + var left = 0, right = nums.count - 1, res = Array(repeating: 0, count: nums.count) + var square = 0, idx = nums.count - 1 + + while left <= right { + + if abs(nums[left]) < abs(nums[right]) { + square = nums[right] + right -= 1 + } else { + square = nums[left] + left += 1 + } + + res[idx] = square * square + idx -= 1 + } + + return res + } +} diff --git a/BFS/BusRoutes.swift b/BFS/BusRoutes.swift new file mode 100644 index 00000000..80fc5c25 --- /dev/null +++ b/BFS/BusRoutes.swift @@ -0,0 +1,58 @@ +/** + * Question Link: https://leetcode.com/problems/bus-routes/ + * Primary idea: BFS. Build a map for stop and related buses' indexes map. Use a queue to track until the current stop is equal to the target. + * + * Time Complexity: O(nm), Space Complexity: O(nm) + * + */ + +class BusRoutes { + func numBusesToDestination(_ routes: [[Int]], _ source: Int, _ target: Int) -> Int { + + if source == target { + return 0 + } + + let stopBusesMap = buildMap(routes) + + // bfs + var queue = [source], res = 0, isVisited = Set() + + while !queue.isEmpty { + let size = queue.count + + for _ in 0.. [Int: Set] { + var stopBusesMap = [Int: Set]() + + for (i, bus) in routes.enumerated() { + bus.forEach { + stopBusesMap[$0, default: Set()].insert(i) + } + } + + return stopBusesMap + } +} diff --git a/BFS/RaceCar.swift b/BFS/RaceCar.swift new file mode 100644 index 00000000..cc74767c --- /dev/null +++ b/BFS/RaceCar.swift @@ -0,0 +1,53 @@ +/** + * Question Link: https://leetcode.com/problems/race-car/ + * Primary idea: BFS to go over A or R at a specific step. Only add R to the queue when necessary. + * + * Time Complexity: O(nlogn), Space Complexity: O(n) + */ + +class RaceCar { + func racecar(_ target: Int) -> Int { + let startNode = Node(speed: 1, position: 0) + var queue = [startNode], len = 0, isVisited = Set() + + while !queue.isEmpty { + let size = queue.count + + for _ in 0.. 0 ? -1 : 1, position: node.position) + queue.append(RNode) + } + + } + + len += 1 + } + + return len + } + + private func shouldRevert(_ node: Node, _ target: Int) -> Bool { + return (node.position + node.speed > target && node.speed > 0) || (node.position + node.speed < target && node.speed < 0) + } + + struct Node: Hashable { + let speed: Int + let position: Int + } +} diff --git a/BFS/ShortestPathGetFood.swift b/BFS/ShortestPathGetFood.swift index 79fc85e6..7f76035e 100644 --- a/BFS/ShortestPathGetFood.swift +++ b/BFS/ShortestPathGetFood.swift @@ -13,29 +13,36 @@ class ShortestPathGetFood { let start = findStart(grid) isVisited[start.0][start.1] = true - var queue = [Point(i: start.0, j: start.1, len: 0)] + var queue = [(start.0, start.1)], count = 0 while !queue.isEmpty { - let point = queue.removeFirst() - if grid[point.i][point.j] == "#" { - return point.len - } + let size = queue.count - for dir in [(0, 1), (0, -1), (1, 0), (-1, 0)] { - let (x, y) = (point.i + dir.0, point.j + dir.1) - - guard x >= 0 && x < m && y >= 0 && y < n && !isVisited[x][y] else { - continue + for _ in 0..= 0 && x < m && y >= 0 && y < n && !isVisited[x][y] else { + continue + } + + if grid[x][y] == "X" { + continue + } + + isVisited[x][y] = true + queue.append((x, y)) } - - isVisited[x][y] = true - queue.append(Point(i: x, j: y, len: point.len + 1)) } + + count += 1 } return -1 @@ -52,10 +59,4 @@ class ShortestPathGetFood { return (-1, -1) } - - struct Point { - var i: Int - var j: Int - var len: Int - } } diff --git a/BFS/ShortestPathGridObstaclesElimination.swift b/BFS/ShortestPathGridObstaclesElimination.swift index 0d36fe14..9e17e296 100644 --- a/BFS/ShortestPathGridObstaclesElimination.swift +++ b/BFS/ShortestPathGridObstaclesElimination.swift @@ -8,47 +8,46 @@ class ShortestPathGridObstaclesElimination { func shortestPath(_ grid: [[Int]], _ k: Int) -> Int { let m = grid.count, n = grid[0].count - var remainings = Array(repeating: Array(repeating: -1, count: n), count: m) + var remaining = Array(repeating: Array(repeating: -1, count: n), count: m) + var queue = [Point(i: 0, j: 0, remain: k)], count = 0 - remainings[0][0] = k - var queue = [Point(i: 0, j: 0, remain: k)] - var count = 0 - - if k > m + n - 2 { - return m + n - 2; + if m + n - 2 < k { + return m + n - 2 } while !queue.isEmpty { + let size = queue.count - let currentPoints = queue - queue.removeAll() - - for point in currentPoints { - if point.i == m - 1 && point.j == n - 1 { + for _ in 0..= 0 && x < m && y >= 0 && y < n else { continue } - - if grid[x][y] == 1 && point.remain <= 0 { + + if grid[x][y] == 1 && point.remain == 0 { continue } - let remain = grid[x][y] == 1 ? point.remain - 1 : point.remain - // only choose the path if remainings are greater - if remainings[x][y] >= remain { + let nextRemaining = grid[x][y] == 1 ? point.remain - 1 : point.remain + + if remaining[x][y] >= nextRemaining { continue - } + } - remainings[x][y] = remain - queue.append(Point(i: x, j: y, remain: remain)) + remaining[x][y] = nextRemaining + queue.append(Point(i: x, j: y, remain: nextRemaining)) + } } + count += 1 } @@ -56,8 +55,8 @@ class ShortestPathGridObstaclesElimination { } struct Point { - var i: Int - var j: Int - var remain: Int + let i: Int + let j: Int + let remain: Int } } \ No newline at end of file diff --git a/BFS/WordLadder.swift b/BFS/WordLadder.swift index 3346c53c..633c8c09 100644 --- a/BFS/WordLadder.swift +++ b/BFS/WordLadder.swift @@ -10,20 +10,26 @@ class WordLadder { func ladderLength(_ beginWord: String, _ endWord: String, _ wordList: [String]) -> Int { - var wordSet = Set(wordList), wordStepQueue = [(beginWord, 1)] + var wordSet = Set(wordList), wordQueue = [beginWord], count = 1 - while !wordStepQueue.isEmpty { - let (currentWord, currentStep) = wordStepQueue.removeFirst() + while !wordQueue.isEmpty { - if currentWord == endWord { - return currentStep - } + let size = wordQueue.count - for word in neighbors(for: currentWord, in: wordSet) { + for _ in 0.. Bool { + let dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)], m = board.count, n = board[0].count + + for i in 0.. Bool { + let m = board.count, n = board[0].count + + return i >= 0 && i < m && j >= 0 && j < n && board[i][j] != "#" + } + + private func dfs(_ idx: Int, _ word: [Character], _ board: [[Character]], _ i: Int, _ j: Int, _ dir: (Int, Int)) -> Bool { + if idx == word.count { + return !isValid(i, j, board) + } + + guard isValid(i, j, board) else { + return false + } + + guard board[i][j] == " " || board[i][j] == word[idx] else { + return false + } + + return dfs(idx + 1, word, board, i + dir.0, j + dir.1, dir) + } +} diff --git a/DFS/CourseSchedule.swift b/DFS/CourseSchedule.swift deleted file mode 100644 index 64aa7396..00000000 --- a/DFS/CourseSchedule.swift +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Question Link: https://leetcode.com/problems/course-schedule/ - * Primary idea: Kahn's Algorithms - * 1) Create the graph - * 2) Decorate each vertex with its in-degree - * 3) Create a set of all sources - * 4) While the set isn’t empty, - * i. Remove a vertex from the set and add it to the sorted list - * ii. For every edge from that vertex: - * - Decrement the in-degree of the destination node - * - Check all of its destination vertices and add them to the set if they have no incoming edges - * Time Complexity: O(|E| + |V|), Space Complexity: O(n^2) - * Recommand Reading: http://cs.brown.edu/courses/csci0160/lectures/14.pdf - */ - -class CourseSchedule { - func canFinish(_ numCourses: Int, _ prerequisites: [[Int]]) -> Bool { - // ATTENTION: if graph use [[Int]], will get 'memory limited exceed' - var graph = [[UInt8]](repeatElement([UInt8](repeatElement(0, count: numCourses)), count: numCourses)) - var indegree = [Int](repeatElement(0, count: numCourses)) - - // 1. Create the graph - for i in 0.. [Int] { - // ATTENTION: if graph use [[Int]], will get 'memory limited exceed' - var graph = [[UInt8]](repeatElement([UInt8](repeatElement(0, count: numCourses)), count: numCourses)) - var indegree = [Int](repeatElement(0, count: numCourses)) - - // 1. Create the graph - for i in 0.. [[Character]] { + let i = click[0], j = click[1] + var board = board + + if board[i][j] == "M" { + board[i][j] = "X".first! + return board + } + + let m = board.count, n = board[0].count + var isVisited = Array(repeating: Array(repeating: false, count: n), count: m) + + dfs(&board, i, j, &isVisited) + + return board + } + + private func dfs(_ board: inout [[Character]], _ i: Int, _ j: Int, _ isVisited: inout [[Bool]]) { + guard isValid(i, j, board) && !isVisited[i][j] else { + return + } + + isVisited[i][j] = true + + if board[i][j] == "E" { + var count = 0 + for dir in dirs { + let x = i + dir.0, y = j + dir.1 + + if isValid(x, y, board) { + if board[x][y] == "X" || board[x][y] == "M" { + count += 1 + } + } + } + + if count == 0 { + board[i][j] = "B".first! + + for dir in dirs { + let x = i + dir.0, y = j + dir.1 + dfs(&board, x, y, &isVisited) + } + } else { + board[i][j] = String(count).first! + } + } + } + + private func isValid(_ i: Int, _ j: Int, _ board: [[Character]]) -> Bool { + return i >= 0 && i < board.count && j >= 0 && j < board[0].count + } +} diff --git a/DP/LongestIncreasingSubsequence.swift b/DP/LongestIncreasingSubsequence.swift index 819439d2..89592487 100644 --- a/DP/LongestIncreasingSubsequence.swift +++ b/DP/LongestIncreasingSubsequence.swift @@ -6,34 +6,34 @@ class LongestIncreasingSubsequence { func lengthOfLIS(_ nums: [Int]) -> Int { - guard let first = nums.first else { - return 0 - } - - var ends = [first] + var res = [nums[0]] for i in 1..= ends.count { - ends.append(nums[i]) + if res.last! < nums[i] { + res.append(nums[i]) } else { - ends[right] = nums[i] + res[binarySearch(res, nums[i])] = nums[i] } } - return ends.count + return res.count + } + + private func binarySearch(_ num: Int, _ res: [Int]) -> Int { + var l = 0, r = res.count - 1 + + while l < r { + let mid = (r - l) / 2 + l + + if res[mid] == num { + return mid + } else if res[mid] > num { + r = mid + } else { + l = mid + 1 + } + } + + return l } -} \ No newline at end of file +} diff --git a/DP/MinimumPathSum.swift b/DP/MinimumPathSum.swift index ab455b1a..39b3c028 100644 --- a/DP/MinimumPathSum.swift +++ b/DP/MinimumPathSum.swift @@ -6,27 +6,23 @@ class MinimumPathSum { func minPathSum(_ grid: [[Int]]) -> Int { - guard grid.count != 0 && grid[0].count != 0 else{ - return 0 - } - let m = grid.count, n = grid[0].count - var dp = Array(repeating: Array(repeating: 0, count: n), count: m) - + var dp = grid + for i in 0.. Int { + guard let node = keyNodeMap[key] else { + return -1 + } + + remove(node) + moveToHead(node) + + return node.val } - func add(_ node: DoublyLinkedList){ - let next = head.next - head.next = node - node.previous = head - node.next = next - next?.previous = node + func put(_ key: Int, _ value: Int) { + let node = Node(key, value) + + if let lastNode = keyNodeMap[key] { + remove(lastNode) + } + + keyNodeMap[key] = node + moveToHead(node) + + if keyNodeMap.count > capacity { + keyNodeMap[tail.prev!.key] = nil + remove(tail.prev!) + } } - func remove(_ node: DoublyLinkedList){ - let previous = node.previous - let next = node.next - previous?.next = next - next?.previous = previous + private func remove(_ node: Node) { + let prev = node.prev + let post = node.next + + prev!.next = post + post!.prev = prev + + node.next = nil + node.prev = nil } - func get(_ key: Int) -> Int{ - if let node = cache[key]{ - remove(node) - add(node) - return node.value - } - return -1 + private func moveToHead(_ node: Node) { + let first = head.next + + head.next = node + + node.prev = head + node.next = first + + first!.prev = node } - func put(_ key: Int, _ value: Int){ - if let node = cache[key]{ - remove(node) - cache.removeValue(forKey: key) - }else if cache.keys.count >= maxCapacity{ - if let leastNode = tail.previous{ - remove(leastNode) - cache.removeValue(forKey: leastNode.key) - } + class Node { + let key: Int + var val: Int + + var prev: Node? + var next: Node? + + init(_ key: Int, _ val: Int) { + self.key = key + self.val = val } - let newNode = DoublyLinkedList(key, value) - cache[key] = newNode - add(newNode) } } + +/** + * Your LRUCache object will be instantiated and called as such: + * let obj = LRUCache(capacity) + * let ret_1: Int = obj.get(key) + * obj.put(key, value) + */ diff --git a/Design/SearchSuggestionsSystem.swift b/Design/SearchSuggestionsSystem.swift new file mode 100644 index 00000000..c31cfc30 --- /dev/null +++ b/Design/SearchSuggestionsSystem.swift @@ -0,0 +1,72 @@ +/** + * Question Link: https://leetcode.com/problems/search-suggestions-system/ + * Primary idea: Use trie to add and search prefix (DFS). + * + * Time Complexity: O(n), Space Complexity: O(n) + * + */ + +class SearchSuggestionsSystem { + func suggestedProducts(_ products: [String], _ searchWord: String) -> [[String]] { + let trie = Trie() + var res = [[String]]() + + products.forEach { trie.insert($0) } + + return (1...searchWord.count).map { trie.searchWords(for: String(searchWord.prefix($0))) } + } + + private class Trie { + let root = TrieNode() + + func insert(_ word: String) { + var node = root + + for char in word { + if node.charNodeMap[char] == nil { + node.charNodeMap[char] = TrieNode() + } + + node = node.charNodeMap[char]! + } + + node.isWord = true + } + + func searchWords(for term: String) -> [String] { + var res = [String](), node = root + + for char in term { + guard let next = node.charNodeMap[char] else { + return res + } + + node = next + } + + dfs(&res, term, node) + + return Array(res.sorted().prefix(3)) + } + + private func dfs(_ res: inout [String], _ path: String, _ node: TrieNode) { + if node.isWord { + res.append(path) + } + + for (char, next) in node.charNodeMap { + dfs(&res, path + String(char), next) + } + } + } + + private class TrieNode { + var isWord: Bool + var charNodeMap: [Character: TrieNode] + + init() { + isWord = false + charNodeMap = [Character: TrieNode]() + } + } +} diff --git a/Design/SnapshotArray.swift b/Design/SnapshotArray.swift new file mode 100644 index 00000000..f75fa4e2 --- /dev/null +++ b/Design/SnapshotArray.swift @@ -0,0 +1,33 @@ +/** + * Question Link: https://leetcode.com/problems/snapshot-array/ + * Primary idea: Use the dictionary to dictionary to hold snapshot id to array data if necessary + * + * Time Complexity: O(1), Space Complexity: O(n) + * + */ + +class SnapshotArray { + + private var snapshotArrayMap: [Int: [Int: Int]] + private var count = 0 + private var array = [Int: Int]() + + init(_ length: Int) { + snapshotArrayMap = [Int: [Int: Int]]() + } + + func set(_ index: Int, _ val: Int) { + array[index] = val + } + + func snap() -> Int { + snapshotArrayMap[count] = array + count += 1 + + return count - 1 + } + + func get(_ index: Int, _ snap_id: Int) -> Int { + return snapshotArrayMap[snap_id]?[index] ?? 0 + } +} diff --git a/Graph/CourseSchedule.swift b/Graph/CourseSchedule.swift index ac2b43e3..f5e217af 100644 --- a/Graph/CourseSchedule.swift +++ b/Graph/CourseSchedule.swift @@ -8,39 +8,28 @@ class CourseSchedule { func canFinish(_ numCourses: Int, _ prerequisites: [[Int]]) -> Bool { - var inDegrees = Array(repeating: 0, count: numCourses), fromTo = [Int: [Int]]() - var coursesCouldTake = [Int](), queue = [Int]() + var inDegrees = Array(repeating: 0, count: numCourses), toCourses = [Int: [Int]]() - // init graph - for prerequisite in prerequisites { - fromTo[prerequisite[1], default: []].append(prerequisite[0]) - inDegrees[prerequisite[0]] += 1 + for courses in prerequisites { + inDegrees[courses[0]] += 1 + toCourses[courses[1], default:[]].append(courses[0]) } - // BFS - for course in 0.. [Int] { - var inDegrees = Array(repeating: 0, count: numCourses), fromTo = [Int: [Int]]() - var coursesCouldTake = [Int](), queue = [Int]() - - // init graph - for prerequisite in prerequisites { - fromTo[prerequisite[1], default: []].append(prerequisite[0]) - inDegrees[prerequisite[0]] += 1 - } - - // BFS - for course in 0.. Int { + let logs = logs.sorted { $0[0] < $1[0] } + + var roots = Array(0.. Int { + var node = node + + while node != roots[node] { + node = roots[node] + } + + return node + } +} diff --git a/Graph/EvaluateDivision.swift b/Graph/EvaluateDivision.swift new file mode 100644 index 00000000..6ea0c775 --- /dev/null +++ b/Graph/EvaluateDivision.swift @@ -0,0 +1,62 @@ +/** + * Question Link: https://leetcode.com/problems/evaluate-division/ + * Primary idea: Classic Union Find. Update roots and values while building the graph. + * + * Time Complexity: O((M + N) * logN), Space Complexity: O(N) + * + */ + +class EvaluateDivision { + func calcEquation(_ equations: [[String]], _ values: [Double], _ queries: [[String]]) -> [Double] { + var res = [Double]() + var graph = union(equations, values) + + for query in queries { + if graph[query[0]] == nil || graph[query[1]] == nil { + res.append(-1.0) + continue + } + + let left = find(&graph, query[0]) + let right = find(&graph, query[1]) + + if left.0 != right.0 { + res.append(-1.0) + } else { + res.append(left.1 / right.1) + } + } + + return res + } + + private func find(_ roots: inout [String: (String, Double)], _ node: String) -> (String, Double) { + if roots[node] == nil { + roots[node] = (node, 1) + } + + var n = roots[node]!.0 + + while n != roots[n]!.0 { + roots[node] = (roots[n]!.0, roots[n]!.1 * roots[node]!.1) + n = roots[n]!.0 + } + + return roots[node]! + } + + private func union(_ equations: [[String]], _ values: [Double]) -> [String: (String, Double)] { + var res = [String: (String, Double)]() + + for i in 0.. Int { + let nums = nums.sorted() + var left = 0, res = 1, sum = 0 + + for (i, num) in nums.enumerated() { + sum += num + + while (i - left + 1) * num - sum > k { + sum -= nums[left] + left += 1 + } + + res = max(res, i - left + 1) + } + + return res + } +} diff --git a/SlidingWindow/LongestContinuousSubarrayLimit.swift b/SlidingWindow/LongestContinuousSubarrayLimit.swift new file mode 100644 index 00000000..362c72fc --- /dev/null +++ b/SlidingWindow/LongestContinuousSubarrayLimit.swift @@ -0,0 +1,41 @@ +/** + * Question Link: https://leetcode.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/ + * Primary idea: Slding window, use max and min queues to track largest difference along the way. Move left pointer to get the potential result. + * + * Note: k may be invalid, mention that with interviewer + * Time Complexity: O(n), Space Complexity: O(n) + * + */ + +class LongestContinuousSubarrayLimit { + func longestSubarray(_ nums: [Int], _ limit: Int) -> Int { + var maxQueue = [Int](), minQueue = [Int](), left = 0, res = 0 + + for (i, num) in nums.enumerated() { + while !maxQueue.isEmpty && maxQueue.last! < num { + maxQueue.removeLast() + } + while !minQueue.isEmpty && minQueue.last! > num { + minQueue.removeLast() + } + + maxQueue.append(num) + minQueue.append(num) + + if maxQueue.first! - minQueue.first! > limit { + if nums[left] == maxQueue.first! { + maxQueue.removeFirst() + } + if nums[left] == minQueue.first! { + minQueue.removeFirst() + } + + left += 1 + } + + res = max(res, i - left + 1) + } + + return res + } +} diff --git a/String/LongestSubstringMostKDistinctCharacters.swift b/SlidingWindow/LongestSubstringMostKDistinctCharacters.swift similarity index 52% rename from String/LongestSubstringMostKDistinctCharacters.swift rename to SlidingWindow/LongestSubstringMostKDistinctCharacters.swift index 8c6e613b..addab4a0 100644 --- a/String/LongestSubstringMostKDistinctCharacters.swift +++ b/SlidingWindow/LongestSubstringMostKDistinctCharacters.swift @@ -13,29 +13,33 @@ class LongestSubstringMostKDistinctCharacters { guard k > 0 else { return 0 } - + + var charFreqMap = [Character: Int](), left = 0, res = 0 let s = Array(s) - var start = 0, longest = 0, charsFreq = [Character: Int]() - + for (i, char) in s.enumerated() { - if let freq = charsFreq[char] { - charsFreq[char] = freq + 1 + if let freq = charFreqMap[char] { + charFreqMap[char] = freq + 1 } else { - while charsFreq.count == k { - longest = max(i - start, longest) - - guard let freq = charsFreq[s[start]] else { + + // update res + res = max(i - left, res) + + // move left and window + while charFreqMap.count == k { + if let leftFreq = charFreqMap[s[left]] { + charFreqMap[s[left]] = leftFreq == 1 ? nil : leftFreq - 1 + left += 1 + } else { fatalError() } - charsFreq[s[start]] = freq == 1 ? nil : freq - 1 - - start += 1 } - - charsFreq[char] = 1 + + // update window for current char + charFreqMap[char] = 1 } } - - return max(longest, s.count - start) + + return max(res, s.count - left) } -} \ No newline at end of file +} diff --git a/SlidingWindow/LongestSubstringMostTwoDistinctCharacters.swift b/SlidingWindow/LongestSubstringMostTwoDistinctCharacters.swift new file mode 100644 index 00000000..6bf3fe2c --- /dev/null +++ b/SlidingWindow/LongestSubstringMostTwoDistinctCharacters.swift @@ -0,0 +1,40 @@ +/** + * Question Link: https://leetcode.com/problems/longest-substring-with-at-most-two-distinct-characters/ + * Primary idea: Slding window. Use char freq map to check substring is valid or not, and + note to handle the end of string edge case + * + * Time Complexity: O(n), Space Complexity: O(n) + * + */ + + class LongestSubstringMostTwoDistinctCharacters { + func lengthOfLongestSubstringTwoDistinct(_ s: String) -> Int { + var charFreqMap = [Character: Int](), left = 0, res = 0 + let s = Array(s) + + for (i, char) in s.enumerated() { + if let freq = charFreqMap[char] { + charFreqMap[char] = freq + 1 + } else { + + // update res + res = max(i - left, res) + + // move left and window + while charFreqMap.count == 2 { + if let leftFreq = charFreqMap[s[left]] { + charFreqMap[s[left]] = leftFreq == 1 ? nil : leftFreq - 1 + left += 1 + } else { + fatalError() + } + } + + // update window for current char + charFreqMap[char] = 1 + } + } + + return max(res, s.count - left) + } +} diff --git a/String/LongestSubstringWithoutRepeatingCharacters.swift b/SlidingWindow/LongestSubstringWithoutRepeatingCharacters.swift similarity index 100% rename from String/LongestSubstringWithoutRepeatingCharacters.swift rename to SlidingWindow/LongestSubstringWithoutRepeatingCharacters.swift diff --git a/String/MinimumWindowSubstring.swift b/SlidingWindow/MinimumWindowSubstring.swift similarity index 100% rename from String/MinimumWindowSubstring.swift rename to SlidingWindow/MinimumWindowSubstring.swift diff --git a/Array/SlidingWindowMaximum.swift b/SlidingWindow/SlidingWindowMaximum.swift similarity index 100% rename from Array/SlidingWindowMaximum.swift rename to SlidingWindow/SlidingWindowMaximum.swift diff --git a/Array/SubarraysKDifferentIntegers.swift b/SlidingWindow/SubarraysKDifferentIntegers.swift similarity index 100% rename from Array/SubarraysKDifferentIntegers.swift rename to SlidingWindow/SubarraysKDifferentIntegers.swift diff --git a/Sort/EmployeeFreeTime.swift b/Sort/EmployeeFreeTime.swift new file mode 100644 index 00000000..753b4aa6 --- /dev/null +++ b/Sort/EmployeeFreeTime.swift @@ -0,0 +1,77 @@ +/** + * Question Link: https://leetcode.com/problems/employee-free-time/ + * Primary idea: Combine and merge sorted arrays. Then iterate through it to get offset between every two elements. + * Time Complexity: O(n), Space Complexity: O(n) + * + * Definition for an Interval. + * public class Interval { + * public var start: Int + * public var end: Int + * public init(_ start: Int, _ end: Int) { + * self.start = start + * self.end = end + * } + * } + */ + +class EmployeeFreeTime { + func employeeFreeTime(_ schedule: [[Interval]]) -> [Interval] { + let intervals = mergeIntervals(combineIntervals(schedule)) + var res = [Interval]() + + for i in 1.. [Interval] { + var res = [Interval]() + + for interval in intervals { + if let last = res.last, last.end >= interval.start { + res.removeLast() + res.append(Interval(last.start, max(last.end, interval.end))) + } else { + res.append(interval) + } + } + + return res + } + + private func combineIntervals(_ schedule: [[Interval]]) -> [Interval] { + var res = schedule[0] + + for i in 1.. [Interval] { + var res = [Interval](), i = 0, j = 0 + + while i < l.count || j < r.count { + if i == l.count { + res.append(r[j]) + j += 1 + } else if j == r.count { + res.append(l[i]) + i += 1 + } else { + if l[i].start <= r[j].start { + res.append(l[i]) + i += 1 + } else { + res.append(r[j]) + j += 1 + } + } + } + + return res + } +} diff --git a/Sort/InsertInterval.swift b/Sort/InsertInterval.swift index 97a19a83..8b3e85e7 100644 --- a/Sort/InsertInterval.swift +++ b/Sort/InsertInterval.swift @@ -21,27 +21,29 @@ class InsertInterval { func insert(_ intervals: [Interval], _ newInterval: Interval) -> [Interval] { - var index = 0 - var result: [Interval] = [] - var tempInterval = Interval(newInterval.start, newInterval.end) - - while index < intervals.count && newInterval.start > intervals[index].end { - result.append(intervals[index]) - index += 1 - } - - while index < intervals.count && newInterval.end >= intervals[index].start { - let minStart = min(tempInterval.start, intervals[index].start) - let maxEnd = max(tempInterval.end, intervals[index].end) - tempInterval = Interval(minStart, maxEnd) - index += 1 + var res = [Interval](), insertIdx = 0 + + for (i, interval) in intervals.enumerated() { + if interval.isOverlap(with: newInterval) { + newInterval.start = min(newInterval.start, interval.start) + newInterval.end = max(newInterval.end, interval.end) + } else { + if interval.end < newInterval.start { + insertIdx += 1 + } + + res.append(interval) + } } - result.append(tempInterval) - - for i in index ..< intervals.count { - result.append(intervals[i]) + + res.insert(newInterval, at: insertIdx) + + return res + } + + extension Interval { + func isOverlap(with interval: Interval) -> Bool { + return start <= interval.end && end >= interval.start } - - return result } } diff --git a/Sort/MergeIntervals.swift b/Sort/MergeIntervals.swift index b83f339e..dc558a34 100644 --- a/Sort/MergeIntervals.swift +++ b/Sort/MergeIntervals.swift @@ -15,30 +15,21 @@ */ class MergeIntervals { - func merge(intervals: [Interval]) -> [Interval] { - var result = [Interval]() - - let intervals = intervals.sorted { - if $0.start != $1.start { - return $0.start < $1.start - } else { - return $0.end < $1.end - } - } - - for interval in intervals { - guard let last = result.last else { - result.append(interval) - continue - } + func merge(_ intervals: [[Int]]) -> [[Int]] { + let intervals = intervals.sorted { return $0[0] < $1[0] } + var res = [intervals[0]] + + for interval in intervals[1.. Bool { + // binary search + var l = 0, r = sortedRanges.count - 1 + + while l <= r { + let m = (r - l) / 2 + l + + if sortedRanges[m].0 <= left && sortedRanges[m].1 >= right { + return true + } else { + if sortedRanges[m].0 > right { + r = m - 1 + } else if sortedRanges[m].1 < left { + l = m + 1 + } else { + return false + } + } + } + + return false + } + + func removeRange(_ left: Int, _ right: Int) { + var idx = 0 + + while idx < sortedRanges.count { + let range = sortedRanges[idx] + + if isOverlap(range, (left, right)) { + if range.0 >= left && range.1 <= right { + sortedRanges.remove(at: idx) + } else if range.0 < left && range.1 > right { + sortedRanges.remove(at: idx) + sortedRanges.insert((right, range.1), at: idx) + sortedRanges.insert((range.0, left), at: idx) + idx += 2 + } else if range.1 <= right { + sortedRanges[idx].1 = left + idx += 1 + } else { + sortedRanges[idx].0 = right + idx += 1 + } + } else { + idx += 1 + } + } + } + + + private func sortRanges(for newRange: (Int, Int)) { + sortedRanges.append(newRange) + + sortedRanges.sort { + $0.0 < $1.0 + } + + var res = [(Int, Int)]() + + for range in sortedRanges { + guard let last = res.last else { + res.append(range) + continue + } + + if range.0 > last.1 { + res.append(range) + } else { + res.removeLast() + res.append((last.0, max(last.1, range.1))) + } + } + + sortedRanges = res + } + + private func isOverlap(_ l: (Int, Int), _ r: (Int, Int)) -> Bool { + return !(l.0 >= r.1 || r.0 >= l.1) + } +} diff --git a/Stack/ValidParentheses.swift b/Stack/ValidParentheses.swift index 302ee9f2..5ac0bf14 100644 --- a/Stack/ValidParentheses.swift +++ b/Stack/ValidParentheses.swift @@ -7,25 +7,28 @@ class ValidParentheses { func isValid(_ s: String) -> Bool { var stack = [Character]() - + for char in s { - if char == "(" || char == "[" || char == "{" { + switch char { + case "(", "[", "{": stack.append(char) - } else if char == ")" { - guard stack.count != 0 && stack.removeLast() == "(" else { + case ")": + if stack.popLast() != "(" { return false } - } else if char == "]" { - guard stack.count != 0 && stack.removeLast() == "[" else { + case "]": + if stack.popLast() != "[" { return false } - } else if char == "}" { - guard stack.count != 0 && stack.removeLast() == "{" else { + case "}": + if stack.popLast() != "{" { return false } + default: + continue } } - + return stack.isEmpty } } \ No newline at end of file diff --git a/String/GroupAnagrams.swift b/String/GroupAnagrams.swift index 08ab82f2..3b8b6ba3 100644 --- a/String/GroupAnagrams.swift +++ b/String/GroupAnagrams.swift @@ -8,14 +8,6 @@ class GroupAnagrams { func groupAnagrams(_ strs: [String]) -> [[String]] { - var sortedStrToStrs = [String: [String]]() - - for str in strs { - let sortedStr = String(str.sorted()) - - sortedStrToStrs[sortedStr, default: []].append(str) - } - - return Array(sortedStrToStrs.values) + return Array(Dictionary(strs.map { (String($0.sorted()), [$0]) }, uniquingKeysWith: +).values) } } diff --git a/String/LongestSubstringMostTwoDistinctCharacters.swift b/String/LongestSubstringMostTwoDistinctCharacters.swift deleted file mode 100644 index ec4537c0..00000000 --- a/String/LongestSubstringMostTwoDistinctCharacters.swift +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Question Link: https://leetcode.com/problems/longest-substring-with-at-most-two-distinct-characters/ - * Primary idea: Slding window, use dictionary to check substring is valid or not, and - note to handle the end of string edge case - * - * Time Complexity: O(n), Space Complexity: O(n) - * - */ - - class LongestSubstringMostTwoDistinctCharacters { - func lengthOfLongestSubstringTwoDistinct(_ s: String) -> Int { - var start = 0, longest = 0, charFreq = [Character: Int]() - let sChars = Array(s) - - for (i, char) in sChars.enumerated() { - if let freq = charFreq[char] { - charFreq[char] = freq + 1 - } else { - if charFreq.count == 2 { - longest = max(longest, i - start) - - while charFreq.count == 2 { - let charStart = sChars[start] - charFreq[charStart]! -= 1 - - if charFreq[charStart] == 0 { - charFreq[charStart] = nil - } - - start += 1 - } - } - - charFreq[char] = 1 - } - } - - return max(longest, sChars.count - start) - } -} \ No newline at end of file diff --git a/String/MultiplyStrings.swift b/String/MultiplyStrings.swift index a8169b91..c1c0302d 100644 --- a/String/MultiplyStrings.swift +++ b/String/MultiplyStrings.swift @@ -18,12 +18,12 @@ class MultiplyStrings { // calculate product for every digit for (i, char1) in num1.enumerated() { - guard let digit1 = Int(String(char1)) else { + guard let digit1 = char1.wholeNumberValue else { fatalError("Invalid Input num1") } for (j, char2) in num2.enumerated() { - guard let digit2 = Int(String(char2)) else { + guard let digit2 = char2.wholeNumberValue else { fatalError("Invalid Input num2") } @@ -44,7 +44,7 @@ class MultiplyStrings { } // trim starting 0s - while !resStr.isEmpty && resStr.first! == "0" { + while resStr.first == "0" { resStr.removeFirst() } diff --git a/String/TextJustification.swift b/String/TextJustification.swift index d2d3b125..90efbb84 100644 --- a/String/TextJustification.swift +++ b/String/TextJustification.swift @@ -7,72 +7,67 @@ class TextJustification { func fullJustify(_ words: [String], _ maxWidth: Int) -> [String] { - var res = [String]() - var last = 0, currentLineLength = 0 - + var res = [String](), rowStart = 0, currentLen = 0 + for (i, word) in words.enumerated() { - if currentLineLength + word.count + (i - last) > maxWidth { - - res.append(buildLine(words, last, i - 1, maxWidth, currentLineLength)) - - last = i - currentLineLength = 0 + + if currentLen + word.count + (i - rowStart) > maxWidth { + res.append(buildRow(rowStart, i - 1, words, maxWidth, currentLen)) + + rowStart = i + currentLen = 0 } - - currentLineLength += word.count + + currentLen += word.count } - - res.append(buildLastLine(words, last, words.count - 1, maxWidth)) - + + res.append(buildLastRow(rowStart, words, maxWidth)) + return res } - - fileprivate func buildLine(_ words: [String], _ start: Int, _ end: Int, _ maxWidth: Int, _ currentLineLength: Int) -> String { - var line = "" - var extraSpaceNum = 0, spaceNum = 0 - + + private func buildRow(_ start: Int, _ end: Int, _ words: [String], _ maxWidth: Int, _ currentLen: Int) -> String { + var res = "", spaceNum = 0, extraSpaceNum = 0 + if end > start { - extraSpaceNum = (maxWidth - currentLineLength) % (end - start) - spaceNum = (maxWidth - currentLineLength) / (end - start) + spaceNum = (maxWidth - currentLen) / (end - start) + extraSpaceNum = (maxWidth - currentLen) % (end - start) } else { - spaceNum = maxWidth - currentLineLength + spaceNum = maxWidth - currentLen } - + for i in start...end { - line.append(words[i]) - + res += words[i] + if start != end && i == end { break - } - - for _ in 0.. 0 { - line.append(" ") + res.append(" ") extraSpaceNum -= 1 } } - - return line + + return res } - - fileprivate func buildLastLine(_ words: [String], _ start: Int, _ end: Int, _ maxWidth: Int) -> String { - var line = "" - - for i in start...end { - line.append(words[i]) - - if i < end { - line.append(" ") + + private func buildLastRow(_ start: Int, _ words: [String], _ maxWidth: Int) -> String { + var res = "" + + for i in start.. Bool { + var i = 0, j = s.count - 1, isDeleted = false let s = Array(s) - return isValid(true, s) || isValid(false, s) - } - - private func isValid(_ skipLeft: Bool, _ s: [Character]) -> Bool { - var i = 0, j = s.count - 1, alreadySkipped = false - + while i < j { - if s[i] == s[j] { - i += 1 - j -= 1 - } else { - if alreadySkipped { + if s[i] != s[j] { + if isDeleted { return false } else { - alreadySkipped = true - if skipLeft { - i += 1 - } else { + if s[i + 1] == s [j] && s[j - 1] == s[i] { + i += 1 j -= 1 + } else if s[i + 1] == s[j] { + i += 1 + isDeleted = true + } else if s[j - 1] == s[i] { + j -= 1 + isDeleted = true + } else { + return false } } + } else { + i += 1 + j -= 1 } } - + return true } } diff --git a/Tree/StepByStepDirectionsBinaryTreeNode.swift b/Tree/StepByStepDirectionsBinaryTreeNode.swift new file mode 100644 index 00000000..93342af1 --- /dev/null +++ b/Tree/StepByStepDirectionsBinaryTreeNode.swift @@ -0,0 +1,67 @@ +/** + * Question Link: https://leetcode.com/problems/step-by-step-directions-from-a-binary-tree-node-to-another/ + * Primary idea: The shortest path should pass LCA. Find paths for two nodes and remove their longgest common prefix. + * Time Complexity: O(n), Space Complexity: O(n) + * + * Definition for a binary tree node. + * public class TreeNode { + * public var val: Int + * public var left: TreeNode? + * public var right: TreeNode? + * public init(_ val: Int) { + * self.val = val + * self.left = nil + * self.right = nil + * } + * } + */ + +class StepByStepDirectionsBinaryTreeNode { + func getDirections(_ root: TreeNode?, _ startValue: Int, _ destValue: Int) -> String { + guard let root = root else { + return "" + } + + let startPath = getPath(root, startValue) + let destPath = getPath(root, destValue) + let len = longestCommonPrefixLen(startPath, destPath) + + return String(repeating: "U", count: startPath.count - len) + destPath.dropFirst(len) + } + + private func longestCommonPrefixLen(_ s: String, _ d: String) -> Int { + var i = 0 + let s = Array(s), d = Array(d) + + while i < min(s.count, d.count) { + if s[i] != d[i] { + break + } + + i += 1 + } + + return i + } + + private func getPath(_ parent: TreeNode, _ val: Int) -> String { + var queue = [(parent, "")] + + while !queue.isEmpty { + let current = queue.removeFirst() + + if current.0.val == val { + return current.1 + } + + if let left = current.0.left { + queue.append((left, current.1 + "L")) + } + if let right = current.0.right { + queue.append((right, current.1 + "R")) + } + } + + return "" + } +}