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 { + var leftCount = 0, res = Array("") + + for char in s { + if char == "(" { + leftCount += 1 + res.append(char) + } else if char == ")" { + if leftCount == 0 { + continue + } else { + leftCount -= 1 + res.append(char) + } + } else { + res.append(char) + } + } + + // remove unnecessary left bracket + if leftCount > 0 { + var i = res.count - 1 + + while i >= 0 { + if res[i] == "(" { + res.remove(at: i) + leftCount -= 1 + + if leftCount == 0 { + break + } + } + + i -= 1 + } + } + + return String(res) + } +} diff --git a/Array/NextPalindromeUsingSameDigits.swift b/Array/NextPalindromeUsingSameDigits.swift new file mode 100644 index 00000000..c3380b59 --- /dev/null +++ b/Array/NextPalindromeUsingSameDigits.swift @@ -0,0 +1,56 @@ +/** + * Question Link: https://leetcode.com/problems/next-palindrome-using-same-digits/ + * Primary idea: Figure out the first half's next permutation, then double it to the result. + * + * Time Complexity: O(n), Space Complexity: O(1) + * + */ + +class NextPalindromeUsingSameDigits { + func nextPalindrome(_ num: String) -> 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/NumMatrix.swift b/Array/NumMatrix.swift index 66bec545..c06f637f 100644 --- a/Array/NumMatrix.swift +++ b/Array/NumMatrix.swift @@ -7,9 +7,18 @@ class NumMatrix { fileprivate var sum: [[Int]] + fileprivate var m: Int + fileprivate var n: Int init(_ matrix: [[Int]]) { - let m = matrix.count, n = matrix[0].count + m = matrix.count + + if m == 0 { + n = 0 + } else { + n = matrix[0].count + } + sum = Array(repeating: Array(repeating: 0, count: n), count: m) for i in 0.. [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/Array/ValidSudoku.swift b/Array/ValidSudoku.swift index e6d6c9d3..13c5bfd7 100644 --- a/Array/ValidSudoku.swift +++ b/Array/ValidSudoku.swift @@ -6,55 +6,35 @@ */ class ValidSudoku { - func isValidSudoku(_ board: [[Character]]) -> Bool { - return areRowsValid(board) && areColsValid(board) && areSubsquaresValid(board) - } - - private func areRowsValid(_ board: [[Character]]) -> Bool { - var existingDigits = Set() - - for i in 0.. Bool { + let len = 9 - return true - } - - private func areColsValid(_ board: [[Character]]) -> Bool { - var existingDigits = Set() + var rowSet = Array(repeating: Set(), count: len) + var colSet = Array(repeating: Set(), count: len) + var boxSet = Array(repeating: Set(), count: len) - for i in 0.. Bool { - var existingDigits = Set() - - for i in stride(from: 0, to: board.count, by: 3) { - for j in stride(from: 0, to: board[0].count, by: 3) { - existingDigits.removeAll() - for m in i..) -> Bool { - if digit == "." { - return true - } - - if set.contains(digit) { + private func isValid(_ set: inout Set, _ char: Character) -> Bool { + if set.contains(char) { return false } else { - set.insert(digit) + set.insert(char) return true } } 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 new file mode 100644 index 00000000..7f76035e --- /dev/null +++ b/BFS/ShortestPathGetFood.swift @@ -0,0 +1,62 @@ +/** + * Question Link: https://leetcode.com/problems/shortest-path-to-get-food/ + * Primary idea: BFS to go over all possible word paths until the cell is food, avoid hitting the obstacle. + * + * Time Complexity: O(nm), Space Complexity: O(nm) + */ + +class ShortestPathGetFood { + func getFood(_ grid: [[Character]]) -> Int { + let m = grid.count, n = grid[0].count + var isVisited = Array(repeating: Array(repeating: false, count: n), count: m) + + let start = findStart(grid) + + isVisited[start.0][start.1] = true + var queue = [(start.0, start.1)], count = 0 + + while !queue.isEmpty { + + let size = queue.count + + 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)) + } + } + + count += 1 + } + + return -1 + } + + private func findStart(_ grid: [[Character]]) -> (Int, Int) { + for i in 0.. Int { + let m = grid.count, n = grid[0].count + var remaining = Array(repeating: Array(repeating: -1, count: n), count: m) + var queue = [Point(i: 0, j: 0, remain: k)], count = 0 + + if m + n - 2 < k { + return m + n - 2 + } + + while !queue.isEmpty { + let size = queue.count + + for _ in 0..= 0 && x < m && y >= 0 && y < n else { + continue + } + + if grid[x][y] == 1 && point.remain == 0 { + continue + } + + let nextRemaining = grid[x][y] == 1 ? point.remain - 1 : point.remain + + if remaining[x][y] >= nextRemaining { + continue + } + + remaining[x][y] = nextRemaining + queue.append(Point(i: x, j: y, remain: nextRemaining)) + + } + } + + count += 1 + } + + return -1 + } + + struct Point { + let i: Int + let j: Int + let remain: Int + } +} \ No newline at end of file diff --git a/BFS/SlidingPuzzle.swift b/BFS/SlidingPuzzle.swift new file mode 100644 index 00000000..0d53aeab --- /dev/null +++ b/BFS/SlidingPuzzle.swift @@ -0,0 +1,81 @@ +/** + * Question Link: https://leetcode.com/problems/sliding-puzzle/ + * Primary idea: BFS to go over 4 directions until the board meets the requirement. + * Please make sure to keep a global set to store visited board. + * + * Time Complexity: O(mn*(mn)!), Space Complexity: O(mn*(mn)!) + * m stands for row num, n stands for col num + * + */ + +class SlidingPuzzle { + private let m = 2 + private let n = 3 + private let expectedBoard = [[1,2,3],[4,5,0]] + + func slidingPuzzle(_ board: [[Int]]) -> Int { + // find start point + let startPoint = findStart(board) + var queue = [ZeroNode(i: startPoint.0, + j: startPoint.1, + board: board, + step: 0)] + + var isVisited = Set<[[Int]]>() + + while !queue.isEmpty { + let current = queue.removeFirst() + + if current.board == expectedBoard { + return current.step + } + + isVisited.insert(current.board) + + for dir in [(0, 1), (0, -1), (1, 0), (-1, 0)] { + let (x, y) = (current.i + dir.0, current.j + dir.1) + + guard x >= 0 && x < m && y >= 0 && y < n else { + continue + } + + var board = current.board + swap(&board, (x, y), (current.i, current.j)) + + if isVisited.contains(board) { + continue + } + + queue.append(ZeroNode(i: x, + j: y, + board: board, + step: current.step + 1)) + } + } + + return -1 + } + + private func swap(_ board: inout [[Int]], _ pointA: (Int, Int), _ pointB: (Int, Int)) { + (board[pointA.0][pointA.1], board[pointB.0][pointB.1]) = (board[pointB.0][pointB.1], board[pointA.0][pointA.1]) + } + + private func findStart(_ board: [[Int]]) -> (Int, Int) { + for i in 0.. Int { - guard beginWord.count == endWord.count else { - return 0 - } - - var queue = [(beginWord, 1)], wordSet = Set(wordList) + var wordSet = Set(wordList), wordQueue = [beginWord], count = 1 - while !queue.isEmpty { - let (word, step) = queue.removeFirst() + while !wordQueue.isEmpty { - if word == endWord { - return step - } + let size = wordQueue.count - // transform word - for i in 0..) -> [String] { + var res = [String]() + + // change character at every offset of the word + for i 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/CombinationSum.swift b/DFS/CombinationSum.swift index 50933144..ac076d1b 100644 --- a/DFS/CombinationSum.swift +++ b/DFS/CombinationSum.swift @@ -2,7 +2,7 @@ * Question Link: https://leetcode.com/problems/combination-sum/ * Primary idea: Classic Depth-first Search * - * Time Complexity: O(n^n), Space Complexity: O(2^n - 1) + * Time Complexity: O(2^n), Space Complexity: O(n) * */ diff --git a/DFS/CombinationSumII.swift b/DFS/CombinationSumII.swift index 2e16a537..9372fd9b 100644 --- a/DFS/CombinationSumII.swift +++ b/DFS/CombinationSumII.swift @@ -2,7 +2,7 @@ * Question Link: https://leetcode.com/problems/combination-sum-ii/ * Primary idea: Classic Depth-first Search * - * Time Complexity: O(n^n), Space Complexity: O(2^n - 2) + * Time Complexity: O(2^n), Space Complexity: O(n) * */ diff --git a/DFS/CombinationSumIII.swift b/DFS/CombinationSumIII.swift index 1d411e09..ec1d840c 100644 --- a/DFS/CombinationSumIII.swift +++ b/DFS/CombinationSumIII.swift @@ -2,7 +2,7 @@ * Question Link: https://leetcode.com/problems/combination-sum-iii/ * Primary idea: Classic Depth-first Search * - * Time Complexity: O(n^n), Space Complexity: O(nCk) + * Time Complexity: O(2^n), Space Complexity: O(n) * */ diff --git a/DFS/Combinations.swift b/DFS/Combinations.swift index a8b17aee..4e01de52 100644 --- a/DFS/Combinations.swift +++ b/DFS/Combinations.swift @@ -2,30 +2,27 @@ * Question Link: https://leetcode.com/problems/combinations/ * Primary idea: Classic Depth-first Search, another version of Subsets * - * Time Complexity: O(n^n), Space Complexity: O(n) + * Time Complexity: O(2^n), Space Complexity: O(n) * */ class Combinations { - func combine(n: Int, _ k: Int) -> [[Int]] { - var res = [[Int]]() - var path = [Int]() - let nums = [Int](1...n) + func combine(_ n: Int, _ k: Int) -> [[Int]] { + var res = [[Int]](), path = [Int]() - _dfs(nums, &res, &path, 0, k) + dfs(&res, &path, 0, Array(1...n), k) return res } - private func _dfs(nums: [Int], inout _ res: [[Int]], inout _ path: [Int], _ index: Int, _ k: Int) { + private func dfs(_ res: inout [[Int]], _ path: inout [Int], _ idx: Int, _ nums: [Int], _ k: Int) { if path.count == k { - res.append([Int](path)) - return + res.append(path) } - for i in index.. 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.. Int { + let m = matrix.count, n = matrix[0].count + var res = 0, cache = Array(repeating: Array(repeating: -1, count: n), count: m) + + for i in 0.. Int { + + if cache[i][j] != -1 { + return cache[i][j] + } + + var longestLength = 1 + + for dir in [(1, 0), (-1, 0), (0, 1), (0, -1)] { + let x = i + dir.0, y = j + dir.1 + + if x >= 0 && x < m && y >= 0 && y < n && matrix[x][y] > matrix[i][j] { + let candidate = dfs(matrix, x, y, m, n, &cache) + 1 + + longestLength = max(longestLength, candidate) + } + } + + cache[i][j] = longestLength + return cache[i][j] + } +} \ No newline at end of file diff --git a/DFS/Minesweeper.swift b/DFS/Minesweeper.swift new file mode 100644 index 00000000..29652b24 --- /dev/null +++ b/DFS/Minesweeper.swift @@ -0,0 +1,64 @@ +/** + * Question Link: https://leetcode.com/problems/minesweeper/ + * Primary idea: Classic Depth-first Search. Check current node and dfs all directions if mine count is 0, update the board accordingly. + * + * Time Complexity: O(mn), Space Complexity: O(mn) + * + */ + +class Minesweeper { + private let dirs = [(0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (-1, 1), (1, -1), (-1, -1)] + + func updateBoard(_ board: [[Character]], _ click: [Int]) -> [[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/DFS/NumberClosedIslands.swift b/DFS/NumberClosedIslands.swift new file mode 100644 index 00000000..3e4c3f95 --- /dev/null +++ b/DFS/NumberClosedIslands.swift @@ -0,0 +1,59 @@ +/** + * Question Link: https://leetcode.com/problems/number-of-closed-islands/ + * Primary idea: Classic Depth-first Search, go up, down, left, right four directions. Return false only it hits the edge. + * + * Time Complexity: O(mn), Space Complexity: O(mn) + * + */ + +class NumberClosedIslands { + func closedIsland(_ grid: [[Int]]) -> Int { + let m = grid.count, n = grid[0].count + + var isVisited = Array(repeating: Array(repeating: false, count: n), count: m), res = 0 + + for i in 0.. Bool { + guard i >= 0 && i < m && j >= 0 && j < n else { + return false + } + + if grid[i][j] == 1 { + return true + } + + isVisited[i][j] = true + + + var up = true, down = true, left = true, right = true + + if i - 1 < 0 || !isVisited[i - 1][j] { + up = dfs(grid, i - 1, j, m, n, &isVisited) + } + if i + 1 >= m || !isVisited[i + 1][j] { + down = dfs(grid, i + 1, j, m, n, &isVisited) + } + if j - 1 < 0 || !isVisited[i][j - 1] { + left = dfs(grid, i, j - 1, m, n, &isVisited) + } + if j + 1 >= n || !isVisited[i][j + 1] { + right = dfs(grid, i, j + 1, m, n, &isVisited) + } + + return up && down && left && right + } +} diff --git a/DFS/PermutationsII.swift b/DFS/PermutationsII.swift index 800e88be..f2747052 100644 --- a/DFS/PermutationsII.swift +++ b/DFS/PermutationsII.swift @@ -10,29 +10,26 @@ class PermutationsII { func permuteUnique(_ nums: [Int]) -> [[Int]] { var res = [[Int]](), path = [Int](), visited = [Bool](repeating: false, count: nums.count) - let nums = nums.sorted(by: <) - - _dfs(&res, &path, nums, &visited) + dfs(&res, &path, &visited, nums.sorted()) return res } - private func _dfs(inout res: [[Int]], inout _ path: [Int], _ nums: [Int], inout _ visited: [Bool]) { - // termination case + private func dfs(_ res: inout [[Int]], _ path: inout [Int], _ isVisited: inout [Bool], _ nums: [Int]) { if path.count == nums.count { res.append(path) return } - for i in 0.. 0 && nums[i] == nums[i - 1] && visited[i - 1]) { - continue + for (i, num) in nums.enumerated() where !isVisited[i] { + if i > 0 && nums[i] == nums[i - 1] && !isVisited[i - 1] { + continue } - path.append(nums[i]) - visited[i] = true - _dfs(&res, &path, nums, &visited) - visited[i] = false + path.append(num) + isVisited[i] = true + dfs(&res, &path, &isVisited, nums) + isVisited[i] = false path.removeLast() } } diff --git a/DFS/Subsets.swift b/DFS/Subsets.swift index c635e337..0a0dbd13 100644 --- a/DFS/Subsets.swift +++ b/DFS/Subsets.swift @@ -2,30 +2,25 @@ * Question Link: https://leetcode.com/problems/subsets/ * Primary idea: Classic Depth-first Search * - * Time Complexity: O(n^n), Space Complexity: O(n) + * Time Complexity: O(n * 2^n), Space Complexity: O(n * 2^n) * */ class Subsets { - func subsets(nums: [Int]) -> [[Int]] { - var res = [[Int]]() - var path = [Int]() + func subsets(_ nums: [Int]) -> [[Int]] { + var res = [[Int]](), path = [Int]() - let nums = nums.sorted(by: <) - - _dfs(&res, &path, nums, 0) + dfs(&res, &path, 0, nums) return res } - private func _dfs(inout res: [[Int]], inout _ path: [Int], _ nums: [Int], _ index: Int) { - // termination case - + private func dfs(_ res: inout [[Int]], _ path: inout [Int], _ idx: Int, _ nums: [Int]) { res.append(path) - for i in index.. [[Int]] { + func subsetsWithDup(_ nums: [Int]) -> [[Int]] { var res = [[Int]](), path = [Int]() - let nums = nums.sorted(by: <) - - _dfs(&res, &path, nums, 0) + dfs(&res, &path, 0, nums.sorted()) return res } - private func _dfs(inout res: [[Int]], inout _ path:[Int], _ nums: [Int], _ index: Int) { + private func dfs(_ res: inout [[Int]], _ path: inout [Int], _ idx: Int, _ nums: [Int]) { res.append(path) - for i in index.. 0 && nums[i] == nums[i - 1] && i != index { - continue + for i in idx.. 0 && nums[i] == nums[i - 1] && i != idx { + continue } path.append(nums[i]) - _dfs(&res, &path, nums, i + 1) + dfs(&res, &path, i + 1, nums) path.removeLast() } } diff --git a/DFS/SudokuSolver.swift b/DFS/SudokuSolver.swift index 3d969fc1..c7a8cd29 100644 --- a/DFS/SudokuSolver.swift +++ b/DFS/SudokuSolver.swift @@ -3,34 +3,34 @@ * Primary idea: Iterate through the whole matrix, try to fill out empty space with all * possible cases and check the vaildity * - * Time Complexity: O(n^4), Space Complexity: O(1) + * Time Complexity: O((9!) ^ 9), Space Complexity: O(1) */ - class SudokuSolver { + +class SudokuSolver { + private let length = 9 + func solveSudoku(_ board: inout [[Character]]) { - guard board.count != 0 || board[0].count != 0 else { - return - } - helper(&board) + dfs(&board) } - private func helper(_ board: inout [[Character]]) -> Bool { - let m = board.count, n = board[0].count - - for i in 0.. Bool { + let candidates = "123456789" + + for i in 0.. Bool { - let m = board.count, n = board[0].count - - // check row - for x in 0.. [String] { + var res = [String](), path = [String]() + + dfs(&res, &path, Array(s), Set(wordDict), 0) + + return res + } + + private func dfs(_ res: inout [String], _ path: inout [String], _ s: [Character], _ dict: Set, _ idx: Int) { + if idx >= s.count { + res.append(path.joined(separator: " ")) + return + } + + for i in idx.. [String] { - var res = [String]() - - let m = board.count - let n = board[0].count - - let trie = _convertToTrie(words) - var visited = [[Bool]](repeating: Array(repeating: false, count: n), count: m) - - for i in 0 ..< m { - for j in 0 ..< n { - _dfs(board, m, n, i, j, &visited, &res, trie, "") + let trie = Trie(words), m = board.count, n = board[0].count + var isVisited = Array(repeating: Array(repeating: false, count: n), count: m), res = Set() + + for i in 0.., _ i: Int, _ j: Int, _ isVisited: inout [[Bool]], _ currentNode: TrieNode, _ currentStr: String, _ m: Int, _ n: Int) { guard i >= 0 && i < m && j >= 0 && j < n else { return } - - // check visited - guard !visited[i][j] else { + + guard !isVisited[i][j] else { return } - - // check is word prefix - let str = str + "\(board[i][j])" - guard trie.isWordPrefix(str) else { + + guard let child = currentNode.children[board[i][j]] else { return } - - // check word exist - if trie.isWord(str) && !res.contains(str) { - res.append(str) + + isVisited[i][j] = true + + let str = currentStr + "\(board[i][j])" + + if child.isEnd { + res.insert(str) } - - // check four directions - visited[i][j] = true - _dfs(board, m, n, i + 1, j, &visited, &res, trie, str) - _dfs(board, m, n, i - 1, j, &visited, &res, trie, str) - _dfs(board, m, n, i, j + 1, &visited, &res, trie, str) - _dfs(board, m, n, i, j - 1, &visited, &res, trie, str) - visited[i][j] = false + + search(board, trie, &res, i + 1, j, &isVisited, child, str, m, n) + search(board, trie, &res, i - 1, j, &isVisited, child, str, m, n) + search(board, trie, &res, i, j + 1, &isVisited, child, str, m, n) + search(board, trie, &res, i, j - 1, &isVisited, child, str, m, n) + + isVisited[i][j] = false } - func _convertToTrie(_ words: [String]) -> Trie { - let trie = Trie() - - for str in words { - trie.insert(str) + class Trie { + var root: TrieNode + + init(_ words: [String]) { + root = TrieNode() + + words.forEach { insert($0) } } - - return trie - } -} - - -class Trie { - var root: TrieNode - - init() { - root = TrieNode() - } - - func insert(_ word: String) { - var node = root - var word = [Character](word.characters) - - for i in 0 ..< word.count { - let c = word[i] - - if node.children[c] == nil { - node.children[c] = TrieNode() + + private func insert(_ word: String) { + var node = root + + for char in word { + if node.children[char] == nil { + node.children[char] = TrieNode() + } + + node = node.children[char]! } - - node = node.children[c]! + + node.isEnd = true } - - node.isEnd = true } - - func isWord(_ word: String) -> Bool { - var node = root - var word = [Character](word.characters) - - for i in 0 ..< word.count { - let c = word[i] - - if node.children[c] == nil { - return false - } - - node = node.children[c]! - } - return node.isEnd - } + class TrieNode { + var isEnd: Bool + var children: [Character: TrieNode] - func isWordPrefix(_ prefix: String) -> Bool { - var node = root - var prefix = [Character](prefix.characters) - - for i in 0 ..< prefix.count { - let c = prefix[i] - - if node.children[c] == nil { - return false - } - - node = node.children[c]! + init() { + isEnd = false + children = [Character: TrieNode]() } - - return true } } - -class TrieNode { - var isEnd: Bool - var children: [Character:TrieNode] - - init() { - isEnd = false - children = [Character:TrieNode]() - } -} \ No newline at end of file diff --git a/DP/EditDistance.swift b/DP/EditDistance.swift index 394860ac..395c4033 100644 --- a/DP/EditDistance.swift +++ b/DP/EditDistance.swift @@ -7,27 +7,24 @@ class EditDistance { func minDistance(word1: String, _ word2: String) -> Int { - let aChars = [Character](word1.characters) - let bChars = [Character](word2.characters) - let aLen = aChars.count - let bLen = bChars.count + let word1Chars = Array(word1), word2Chars = Array(word2), m = word1.count, n = word2.count + var distances = Array(repeating: Array(repeating: 0, count: n + 1), count: m + 1) - var dp = Array(count: aLen + 1, repeatedValue:(Array(count: bLen + 1, repeatedValue: 0))) - - for i in 0...aLen { - for j in 0...bLen { + for i in 0...m { + for j in 0...n { if i == 0 { - dp[i][j] = j + distances[i][j] = j } else if j == 0 { - dp[i][j] = i - } else if aChars[i - 1] == bChars[j - 1] { - dp[i][j] = dp[i - 1][j - 1] + distances[i][j] = i + } else if word1Chars[i - 1] == word2Chars[j - 1] { + distances[i][j] = distances[i - 1][j - 1] } else { - dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 + distances[i][j] = min(distances[i - 1][j - 1], distances[i - 1][j], distances[i][j - 1]) + 1 } + } } - return dp[aLen][bLen] + return distances[m][n] } } \ No newline at end of file diff --git a/DP/LongestCommonSubsequence.swift b/DP/LongestCommonSubsequence.swift new file mode 100644 index 00000000..870775e5 --- /dev/null +++ b/DP/LongestCommonSubsequence.swift @@ -0,0 +1,26 @@ +/** + * Question Link: https://leetcode.com/problems/longest-common-subsequence/ + * Primary idea: Dynamic Programming, dp[i][j] = dp[i - 1][j - 1] + 1 or max(dp[i - 1][j], dp[i][j - 1]) + * + * Time Complexity: O(mn), Space Complexity: O(1) + */ + +class LongestCommonSubsequence { + func longestCommonSubsequence(_ text1: String, _ text2: String) -> Int { + let text1Chars = Array(text1), text2Chars = Array(text2) + let m = text1.count, n = text2.count + var dp = Array(repeating: Array(repeating: 0, count: n + 1), count: m + 1) + + for i in 1...m { + for j in 1...n { + if text1Chars[i - 1] == text2Chars[j - 1] { + dp[i][j] = dp[i - 1][j - 1] + 1 + } else { + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + } + } + } + + return dp[m][n] + } +} \ No newline at end of file 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/MaximumNumberPointsCost.swift b/DP/MaximumNumberPointsCost.swift new file mode 100644 index 00000000..89895b09 --- /dev/null +++ b/DP/MaximumNumberPointsCost.swift @@ -0,0 +1,33 @@ +/** + * Question Link: https://leetcode.com/problems/maximum-number-of-points-with-cost/ + * Primary idea: DP. dp[i][j] is the maximum number of points we can have if points[i][j] is the most recent cell we picked. + * Optimization: Keeps track the current row is enough. Update current row max by traversing from left and right. + * Time Complexity: O(mn), Space Complexity: O(1) + * + */ + +class MaximumNumberPointsCost { + func maxPoints(_ points: [[Int]]) -> Int { + + let m = points.count, n = points[0].count + var rowMaxes = points[0] + + for i in 1.. Int { + let jobs = constructJobs(startTime, endTime, profit) + var maxProfits = Array(repeating: -1, count: jobs.count), maxProfit = Int.min + + for i in 0.. Int { + if index == jobs.count { + return 0 + } + + if maxProfits[index] != -1 { + return maxProfits[index] + } + + let nextIndex = findNextIndex(jobs, index) + + let maxProfit = max(findMaxProfit(jobs, index + 1, &maxProfits), jobs[index].profit + findMaxProfit(jobs, nextIndex, &maxProfits)) + + maxProfits[index] = maxProfit + + return maxProfit + } + + private func findNextIndex(_ jobs: [Job], _ index: Int) -> Int { + var left = index, right = jobs.count - 1 + var mid = 0 + + while left <= right { + mid = (right - left) / 2 + left + + // overlap + if jobs[index].endTime <= jobs[mid].startTime { + right = mid - 1 + } else { + left = mid + 1 + } + } + + return right + 1 + } + + + private func constructJobs(_ startTime: [Int], _ endTime: [Int], _ profit: [Int]) -> [Job] { + return (0.. 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.. Bool { - let sChars = Array(s), pChars = Array(p) - var dp = Array(repeating: Array(repeating: false, count: pChars.count + 1), count: sChars.count + 1) - dp[0][0] = true + let s = Array(s), p = Array(p), m = s.count, n = p.count + var dp = Array(repeating: Array(repeating: false, count: n + 1), count: m + 1) - for i in 0...pChars.count { - // jump over "" vs. "x*" case - dp[0][i] = i == 0 || i > 1 && dp[0][i - 2] && pChars[i - 1] == "*" + for j in 0...n { + dp[0][j] = j == 0 || (j > 1 && dp[0][j - 2] && p[j - 1] == "*") } - for i in 0...sChars.count { - for j in 0...pChars.count { - guard j > 0 else { - continue - } - - let pCurrent = pChars[j - 1] - - if pCurrent != "*" { - dp[i][j] = i > 0 && dp[i - 1][j - 1] && (pCurrent == "." || pCurrent == sChars[i - 1]) + for i in 1...m { + for j in 1...n { + if p[j - 1] != "*" { + if p[j - 1] == s[i - 1] || p[j - 1] == "." { + dp[i][j] = dp[i - 1][j - 1] + } else { + dp[i][j] = false + } } else { - dp[i][j] = dp[i][j - 2] || i > 0 && j > 1 && (sChars[i - 1] == pChars[j - 2] || pChars[j - 2] == ".") && dp[i - 1][j] + if j > 1 { + dp[i][j] = dp[i][j - 2] || ((p[j - 2] == s[i - 1] || p[j - 2] == ".") && dp[i - 1][j]) + } } } } - return dp[sChars.count][pChars.count] + return dp[m][n] } } \ No newline at end of file diff --git a/DP/UniquePathsII.swift b/DP/UniquePathsII.swift index 118a3e60..8724b14c 100644 --- a/DP/UniquePathsII.swift +++ b/DP/UniquePathsII.swift @@ -5,37 +5,29 @@ */ class UniquePathsII { - func uniquePathsWithObstacles(_ obstacleGrid: [[Int]]) -> Int { - let m = obstacleGrid.count - guard m > 0 else { - return 0 - } + func uniquePathsWithObstacles(_ obstacleGrid: [[Int]]) -> Int { + let m = obstacleGrid.count, n = obstacleGrid[0].count - let n = obstacleGrid[0].count - guard n > 0 else { - return 0 - } - - var dp = Array(repeating: Array(repeating: -1, count: n), count: m) + var dp = Array(repeating: Array(repeating: 0, count: n), count: m) - return help(m - 1, n - 1, &dp, obstacleGrid) - } - - fileprivate func help(_ m: Int, _ n: Int, _ dp: inout [[Int]], _ obstacleGrid: [[Int]]) -> Int { - if m < 0 || n < 0 { - return 0 - } - if obstacleGrid[m][n] == 1 { - return 0 - } - if m == 0 && n == 0 { - return 1 - } - if dp[m][n] != -1 { - return dp[m][n] + for i in 0.. Bool { - let sChars = Array(s), pChars = Array(p) - var dp = Array(repeating: Array(repeating: false, count: p.count + 1), count: s.count + 1) - dp[0][0] = true + let s = Array(s), p = Array(p), m = s.count, n = p.count + var dp = Array(repeating: Array(repeating: false, count: n + 1), count: m + 1) - // must start from 0, to make range feasible and handle empty vs. * case - for i in 0...s.count { - for j in 0...p.count { - guard j > 0 else { - continue - } - - let pCurrent = pChars[j - 1] - - if pCurrent != "*" { - dp[i][j] = i > 0 && dp[i - 1][j - 1] && (pCurrent == sChars[i - 1] || pCurrent == "?") - } else { - // Two situations: - // (1) '*' is the first character in p; - // (2) For k>=0 and k<=i, there is some dp[k][j-1] being true; - // and '*' will match the rest sequence in s after index k; - var flag = false - for k in 0...i { - if dp[k][j - 1] { - flag = true - break - } + for j in 0...n { + dp[0][j] = j == 0 || (dp[0][j - 1] && p[j - 1] == "*") + } + + if m < 1 || n < 1 { + return dp[m][n] + } + + for i in 1...m { + for j in 1...n { + if p[j - 1] != "*" { + if p[j - 1] == s[i - 1] || p[j - 1] == "?" { + dp[i][j] = dp[i - 1][j - 1] + } else { + dp[i][j] = false } - - dp[i][j] = flag || j == 1 + } else { + dp[i][j] = dp[i][j - 1] || dp[i - 1][j] } } } - return dp[s.count][p.count] + return dp[m][n] } } \ No newline at end of file diff --git a/DP/WordBreak.swift b/DP/WordBreak.swift new file mode 100644 index 00000000..58ee5a00 --- /dev/null +++ b/DP/WordBreak.swift @@ -0,0 +1,26 @@ +/** + * Question Link: https://leetcode.com/problems/word-break/ + * Primary idea: DP. dp[i] = dp[j] && dict.contains(s[j.. Bool { + let dict = Set(wordDict), s = Array(s) + var dp = Array(repeating: false, count: s.count + 1) + dp[0] = true + + for i in 1...s.count { + for j in 0.. Int { + let x = point[0], y = point[1] + + guard let xEqualPoints = xPoints[x], let yEqualPoints = yPoints[y] else { + return 0 + } + + var res = 0 + + for (yEqualPointX, firstPointsCount) in yEqualPoints { + if x == yEqualPointX { + continue + } + let sideLength = abs(x - yEqualPointX) + // check bottom square + if let secondPointCount = xEqualPoints[y - sideLength] { + if let thirdPointCount = xPoints[yEqualPointX]?[y - sideLength] { + res += firstPointsCount * secondPointCount * thirdPointCount + } + } + if let secondPointCount = xEqualPoints[y + sideLength] { + if let thirdPointCount = xPoints[yEqualPointX]?[y + sideLength] { + res += firstPointsCount * secondPointCount * thirdPointCount + } + } + } + + return res + } +} + +/** + * Your DetectSquares object will be instantiated and called as such: + * let obj = DetectSquares() + * obj.add(point) + * let ret_2: Int = obj.count(point) + */ diff --git a/Design/GuessWord.swift b/Design/GuessWord.swift new file mode 100644 index 00000000..302ea750 --- /dev/null +++ b/Design/GuessWord.swift @@ -0,0 +1,48 @@ +/** + * Question Link: https://leetcode.com/problems/guess-the-word/ + * Primary idea: Random select a word and check the match count. Filter all words having the same match count. + * + * Time Complexity: O(n), Space Complexity: O(n) + * + * // This is the Master's API interface. + * // You should not implement it, or speculate about its implementation + * class Master { + * public func guess(word: String) -> Int {} + * } + */ + +class GuessWord { + func findSecretWord(_ words: [String], _ master: Master) { + var words = words + + for i in 0..<30 { + let trial = words[words.count / 2] + let count = master.guess(trial) + + if count == 6 { + return + } + + var possibilities = [String]() + for word in words { + if matchCount(trial, word) == count { + possibilities.append(word) + } + } + words = possibilities + } + + } + + private func matchCount(_ wordA: String, _ wordB: String) -> Int { + var res = 0 + + for (charA, charB) in zip(wordA, wordB) { + if charA == charB { + res += 1 + } + } + + return res + } +} diff --git a/Design/LRUCache.swift b/Design/LRUCache.swift index 1158a90c..ac7fb6c6 100644 --- a/Design/LRUCache.swift +++ b/Design/LRUCache.swift @@ -5,71 +5,87 @@ * */ -class DoublyLinkedList{ - var key: Int - var value: Int - var previous: DoublyLinkedList? - var next: DoublyLinkedList? - var hashValue: Int +class LRUCache { - init(_ key: Int, _ value: Int) { - self.key = key - self.value = value - self.hashValue = key - } -} + private let capacity: Int + private let head = Node(0, 0) + private let tail = Node(0, 0) + + private var keyNodeMap = [Int: Node]() -class LRUCache{ - var maxCapacity: Int - var head: DoublyLinkedList - var tail: DoublyLinkedList - var cache: [Int: DoublyLinkedList] + init(_ capacity: Int) { + self.capacity = capacity + + head.next = tail + tail.prev = head + } - init(_ maxCapacity: Int) { - self.maxCapacity = maxCapacity - self.cache = [Int: DoublyLinkedList]() - self.head = DoublyLinkedList(Int.min, Int.min) - self.tail = DoublyLinkedList(Int.max, Int.max) - self.head.next = self.tail - self.tail.previous = self.head + func get(_ key: Int) -> 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/AlienDictionary.swift b/Graph/AlienDictionary.swift index 0630d717..41251259 100644 --- a/Graph/AlienDictionary.swift +++ b/Graph/AlienDictionary.swift @@ -9,68 +9,65 @@ class AlienDictionary { func alienOrder(_ words: [String]) -> String { - var res = "", queueChars = [Character]() - var (indegrees, charToChars) = initGraph(words) + var (inDegrees, toChars) = buildGraph(words) - indegrees.keys.filter { indegrees[$0] == 0 }.forEach { queueChars.append($0) } + var queue = inDegrees.keys.filter { inDegrees[$0] == 0 } + var res = "" - while !queueChars.isEmpty { - let char = queueChars.removeFirst() - res.append(char) + while !queue.isEmpty { + let char = queue.removeFirst() - guard let toChars = charToChars[char] else { - fatalError("Init Graph Error") - } + res.append(char) - for toChar in toChars { - guard let indegree = indegrees[toChar] else { - fatalError("Init Graph Error") - } + for nextChar in toChars[char]! { + inDegrees[nextChar]! -= 1 - indegrees[toChar] = indegree - 1 - if indegree == 1 { - queueChars.append(toChar) + if inDegrees[nextChar] == 0 { + queue.append(nextChar) } } } - return res.count == indegrees.count ? res : "" + return res.count == inDegrees.count ? res : "" } - private func initGraph(_ words: [String]) -> ([Character: Int], [Character: [Character]]) { - var indegrees = [Character: Int](), charToChars = [Character: [Character]]() + private func buildGraph(_ words: [String]) -> ([Character: Int], [Character: [Character]]) { + // init inDegrees and toChars + var inDegrees = [Character: Int](), toChars = [Character: [Character]]() - // init indegress and charToChars words.forEach { word in word.forEach { char in - indegrees[char] = 0 - charToChars[char] = [Character]() - } + inDegrees[char] = 0 + toChars[char] = [Character]() + } } - // refactor indegress and charToChars based on words + // update based on orders for i in 0.. Int { + var srcDestinations = buildGraph(flights) + var queue = [(src, 0)], dstPrice = [Int: Int](), stopCount = 0 + + while !queue.isEmpty && stopCount < k + 1 { + + let currentQueueLen = queue.count + + for _ in 0.. (dst, price) + private func buildGraph(_ flights: [[Int]]) -> [Int: [(Int, Int)]] { + var srcDestinations = [Int: [(Int, Int)]]() + + for flight in flights { + srcDestinations[flight[0], default:[(Int, Int)]()].append((flight[1], flight[2])) + } + + return srcDestinations + } +} \ No newline at end of file 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.. [String] { + var (inDegrees, toRecipes) = buildGraph(recipes, ingredients) + var res = [String](), queue = supplies + + while !queue.isEmpty { + let food = queue.removeFirst() + + for recipe in toRecipes[food] ?? [] { + inDegrees[recipe]! -= 1 + + if inDegrees[recipe] == 0 { + res.append(recipe) + queue.append(recipe) + } + } + } + + return res + } + + private func buildGraph(_ recipes: [String], _ ingredients: [[String]]) -> ([String: Int], [String: [String]]) { + var inDegrees = [String: Int]() + var toRecipes = [String: [String]]() + + for i in 0.. [String] { + var currentIPInt = ipToInt(ip) + var res = [String](), n = n + + while n > 0 { + // get the most right one bit + var step = currentIPInt & -currentIPInt + + if step == 0 { + step = Int(pow(Double(2), Double(32))) + } + + while step > n { + step /= 2 + } + + res.append(IntToIP(currentIPInt, step)) + + currentIPInt += step + n -= step + } + + return res + } + + private func ipToInt(_ ip: String) -> Int { + var x = 0 + let strings = ip.split(separator: ".") + + for str in strings { + x = x * BASE + Int(str)! + } + + return x + } + + private func IntToIP(_ x: Int, _ step: Int) -> String { + var res = Array(""), x = x + + for i in 0..<4 { + res.insert(contentsOf: String(x % BASE), at: 0) + if i != 3 { + res.insert(".", at: 0) + } + x /= BASE + } + + var len = 33, step = step + while step > 0 { + len -= 1 + step /= 2 + } + + res += "/\(len)" + + return String(res) + } +} diff --git a/Math/MaximumSplitPositiveEvenIntegers.swift b/Math/MaximumSplitPositiveEvenIntegers.swift new file mode 100644 index 00000000..fcd7f338 --- /dev/null +++ b/Math/MaximumSplitPositiveEvenIntegers.swift @@ -0,0 +1,25 @@ +/** + * Question Link: https://leetcode.com/problems/maximum-split-of-positive-even-integers/ + * Primary idea: Greedy. The most split should contain numbers as small as possible. + * Time Complexity: O(logn), Space Complexity: O(1) + * + */ + +class MaximumSplitPositiveEvenIntegers { + func maximumEvenSplit(_ finalSum: Int) -> [Int] { + guard finalSum % 2 == 0 else { + return [] + } + + var res = [Int](), candidate = 2, currentSum = 0 + + while (currentSum + candidate) <= finalSum { + res.append(candidate) + currentSum += candidate + candidate += 2 + } + res[res.count - 1] += finalSum - currentSum + + return res + } +} diff --git a/Math/PourWater.swift b/Math/PourWater.swift new file mode 100644 index 00000000..70967aa0 --- /dev/null +++ b/Math/PourWater.swift @@ -0,0 +1,50 @@ +/** + * Question Link: https://leetcode.com/problems/pour-water/ + * Primary idea: One pointer. Go left and then right to update the drop pointer. + * Time Complexity: O(nk), Space Complexity: O(1) + * + */ + + class PourWater { + func pourWater(_ heights: [Int], _ volume: Int, _ k: Int) -> [Int] { + var result = heights, idx = k, dropIdx = k + + for _ in 0..= 0 { + if result[idx] > result[dropIdx] { + break + } + + if result[idx] < result[dropIdx] { + dropIdx = idx + } + + idx -= 1 + } + + // check right + if dropIdx == k { + idx = k + 1 + while idx < result.count { + if result[idx] > result[dropIdx] { + break + } + + if result[idx] < result[dropIdx] { + dropIdx = idx + } + + idx += 1 + } + } + + result[dropIdx] += 1 + } + + return result + } +} \ No newline at end of file diff --git a/README.md b/README.md index 97a0cc4c..5a1a1102 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![Leetcode](./logo.png?style=centerme) ## Progress -[Problem Status](#problem-status) shows the latest progress to all 1000+ questions. Currently we have 323 completed solutions. Note: questions with ♥ mark means that you have to **Subscript to premium membership** of LeetCode to unlock them. +[Problem Status](#problem-status) shows the latest progress to all 1000+ questions. Currently we have 400+ completed solutions. Note: questions with ♥ mark means that you have to **Subscript to premium membership** of LeetCode to unlock them. ## Contributors @@ -157,6 +157,7 @@ [Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/)| [Swift](./LinkedList/MergeTwoSortedLists.swift)| Easy| O(n)| O(1)| [Merge k Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/)| [Swift](./LinkedList/MergeKSortedLists.swift)| Hard| O(mlogn)| O(1)| [Partition List](https://leetcode.com/problems/partition-list/)| [Swift](./LinkedList/PartitionList.swift)| Medium| O(n)| O(1)| +[Reverse Nodes in k-Group](https://leetcode.com/problems/reverse-nodes-in-k-group/)| [Swift](./LinkedList/ReverseNodesInKGroup.swift)| Hard| O(n)| O(1)| [LRU Cache](https://leetcode.com/problems/lru-cache/) | [Swift](./LinkedList/LRUCache.swift) | Hard| O(1)| O(1)| [LFU Cache](https://leetcode.com/problems/lfu-cache/) | [Swift](./LinkedList/LFUCache.swift) | Hard| O(1)| O(1)| @@ -211,7 +212,7 @@ [Path Sum](https://leetcode.com/problems/path-sum/)| [Swift](./Tree/PathSum.swift)| Easy| O(n)| O(n)| [Path Sum II](https://leetcode.com/problems/path-sum-ii/)| [Swift](./Tree/PathSumII.swift)| Medium| O(n)| O(n)| [Path Sum III](https://leetcode.com/problems/path-sum-iii/)| [Swift](./Tree/PathSumIII.swift)| Easy| O(n^2)| O(1)| -[Bnary Tree Paths](https://leetcode.com/problems/binary-tree-paths/)| [Swift](./Tree/BnaryTreePaths.swift)| Easy| O(n)| O(n)| +[Binary Tree Paths](https://leetcode.com/problems/binary-tree-paths/)| [Swift](./Tree/BinaryTreePaths.swift)| Easy| O(n)| O(n)| [Binary Tree Maximum Path Sum](https://leetcode.com/problems/binary-tree-maximum-path-sum/)| [Swift](./Tree/BinaryTreeMaximumPathSum.swift)| Hard| O(n)| O(1)| [House Robber III](https://leetcode.com/problems/house-robber-iii/)| [Swift](./Tree/HouseRobberIII.swift)| Medium| O(n)| O(1)| [Unique Binary Search Trees](https://leetcode.com/problems/unique-binary-search-trees/)| [Swift](./Tree/UniqueBinarySearchTrees.swift)| Medium| O(n^2)| O(n)| @@ -245,6 +246,7 @@ [Paint House](https://leetcode.com/problems/paint-house/)| [Swift](./DP/PaintHouse.swift)| Easy| O(n)| O(n)| [Paint House II](https://leetcode.com/problems/paint-house-ii/)| [Swift](./DP/PaintHouseII.swift)| Hard| O(n)| O(1)| [Longest Increasing Subsequence](https://leetcode.com/problems/longest-increasing-subsequence/)| [Swift](./DP/LongestIncreasingSubsequence.swift)| Medium| O(nlogn)| O(n)| +[Longest Common Subsequence](https://leetcode.com/problems/longest-increasing-subsequence/)| [Swift](./DP/LongestCommonSubsequence.swift)| Medium| O(mn)| O(1)| [Palindromic Substrings](https://leetcode.com/problems/palindromic-substrings/)| [Swift](./DP/PalindromicSubstrings.swift)| Medium| O(n^2)| O(n^2)| [Longest Palindromic Substring](https://leetcode.com/problems/longest-palindromic-substring/)| [Swift](./DP/LongestPalindromicSubstring.swift)| Medium| O(n^2)| O(n^2)| [Perfect Squares](https://leetcode.com/problems/perfect-squares/)| [Swift](./DP/PerfectSquares.swift)| Medium| O(n^2)| O(n)| @@ -253,6 +255,7 @@ [Paint Fence](https://leetcode.com/problems/paint-fence/)| [Swift](./DP/PaintFence.swift)| Easy| O(n)| O(n)| [Maximum Subarray](https://leetcode.com/problems/maximum-subarray/)| [Swift](./DP/MaximumSubarray.swift)| Medium| O(n)| O(1)| [Maximum Product Subarray](https://leetcode.com/problems/maximum-product-subarray/)| [Swift](./DP/MaximumProductSubarray.swift)| Medium| O(n)| O(1)| +[Maximum Number of Points with Cost](https://leetcode.com/problems/maximum-number-of-points-with-cost/)| [Swift](./DP/MaximumNumberPointsCost.swift)| Medium| O(mn)| O(1)| [Maximal Square](https://leetcode.com/problems/maximal-square/)| [Swift](./DP/MaximalSquare.swift)| Medium| O(mn)| O(mn)| [Edit Distance](https://leetcode.com/problems/edit-distance/)| [Swift](./DP/EditDistance.swift)| Hard| O(mn)| O(mn)| [Combination Sum IV](https://leetcode.com/problems/combination-sum-iv/)| [Swift](./DP/CombinationSumIV.swift)| Medium| O(2^n)| O(n)| @@ -271,14 +274,14 @@ ## Depth-first search | Title | Solution | Difficulty | Time | Space | | ----- | -------- | ---------- | ---- | ----- | -[Permutations](https://leetcode.com/problems/permutations/)| [Swift](./DFS/Permutations.swift)| Medium| O(n^n)| O(n)| -[Permutations II](https://leetcode.com/problems/permutations-ii/)| [Swift](./DFS/PermutationsII.swift)| Medium| O(n^n)| O(n)| +[Permutations](https://leetcode.com/problems/permutations/)| [Swift](./DFS/Permutations.swift)| Medium| O(2^n)| O(n)| +[Permutations II](https://leetcode.com/problems/permutations-ii/)| [Swift](./DFS/PermutationsII.swift)| Medium| O(2^n)| O(n)| [Subsets](https://leetcode.com/problems/subsets/)| [Swift](./DFS/Subsets.swift)| Medium| O(n^n)| O(n)| -[Subsets II](https://leetcode.com/problems/subsets-ii/)| [Swift](./DFS/SubsetsII.swift)| Medium| O(n^n)| O(n)| -[Combinations](https://leetcode.com/problems/combinations/)| [Swift](./DFS/Combinations.swift)| Medium| O(n^n)| O(n)| -[Combination Sum](https://leetcode.com/problems/combination-sum/)| [Swift](./DFS/CombinationSum.swift)| Medium| O(n^n)| O(2^n - 1)| -[Combination Sum II](https://leetcode.com/problems/combination-sum-ii/)| [Swift](./DFS/CombinationSumII.swift)| Medium| O(n^n)| O(2^n - 2)| -[Combination Sum III](https://leetcode.com/problems/combination-sum-iii/)| [Swift](./DFS/CombinationSumIII.swift)| Medium| O(n^n)| O(nCk)| +[Subsets II](https://leetcode.com/problems/subsets-ii/)| [Swift](./DFS/SubsetsII.swift)| Medium| O(2^n)| O(n)| +[Combinations](https://leetcode.com/problems/combinations/)| [Swift](./DFS/Combinations.swift)| Medium| O(2^n)| O(n)| +[Combination Sum](https://leetcode.com/problems/combination-sum/)| [Swift](./DFS/CombinationSum.swift)| Medium| O(2^n)| O(n)| +[Combination Sum II](https://leetcode.com/problems/combination-sum-ii/)| [Swift](./DFS/CombinationSumII.swift)| Medium| O(2^n)| O(n)| +[Combination Sum III](https://leetcode.com/problems/combination-sum-iii/)| [Swift](./DFS/CombinationSumIII.swift)| Medium| O(2^n)| O(n)| [Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/)| [Swift](./DFS/LetterCombinationsPhoneNumber.swift)| Medium| O(4^n)| O(n)| [Factor Combinations](https://leetcode.com/problems/factor-combinations/)| [Swift](./DFS/FactorCombinations.swift)| Medium| O(n^n))| O(2^n - 1)| [Strobogrammatic Number II](https://leetcode.com/problems/strobogrammatic-number-ii/)| [Swift](./DFS/StrobogrammaticNumberII.swift)| Medium| O(m^n)| O(n)| @@ -340,7 +343,8 @@ [Sparse Matrix Multiplication](https://leetcode.com/problems/sparse-matrix-multiplication/)| [Swift](./Math/SparseMatrixMultiplication.swift)| Medium| O(n^3)| O(n^2)| [Rectangle Area](https://leetcode.com/problems/rectangle-area/)| [Swift](./Math/RectangleArea.swift)| Easy| O(1)| O(1)| [Minimum Moves to Equal Array Elements](https://leetcode.com/problems/minimum-moves-to-equal-array-elements/)| [Swift](./Math/MinimumMovesEqualArrayElements.swift)| Easy| O(n)| O(1)| -[Trapping Rain Water](https://leetcode.com/problems/trapping-rain-water/)| [Swift](./Math/TrappingRainWater.swift)| Hard| O(n)| O(n)| +[Pour Water](https://leetcode.com/problems/pour-water/)| [Swift](./Math/TrappingRainWater.swift)| Hard| O(n)| O(n)| +[Trapping Rain Water](https://leetcode.com/problems/trapping-rain-water/)| [Swift](./Math/TrappingRainWater.swift)| Medium| O(nk)| O(1)| [Container With Most Water](https://leetcode.com/problems/container-with-most-water/)| [Swift](./Math/ContainerMostWater.swift)| Medium| O(n)| O(1)| [Counting Bits](https://leetcode.com/problems/counting-bits/)| [Swift](./Math/CountingBits.swift)| Medium| O(n)| O(n)| [K-th Smallest in Lexicographical Order](https://leetcode.com/problems/k-th-smallest-in-lexicographical-order/)| [Swift](./Math/KthSmallestLexicographicalOrder.swift)| Hard| O(n)| O(1)| @@ -414,6 +418,7 @@ ## Google | Title | Solution | Difficulty | Frequency | | ----- | -------- | ---------- | --------- | +[Race Car](https://leetcode.com/problems/race-car/)| [Swift](./BFS/RaceCar.swift)| Hard| ★★★★★★| [Plus One](https://leetcode.com/problems/plus-one/)| [Swift](./Math/PlusOne.swift)| Easy| ★★★★★★| [Number of Islands](https://leetcode.com/problems/number-of-islands/)| [Swift](./DFS/NumberofIslands.swift)| Medium| ★★★★| [Summary Ranges](https://leetcode.com/problems/summary-ranges/)| [Swift](./Array/SummaryRanges.swift)| Medium| ★★★★| @@ -441,7 +446,7 @@ [Remove Invalid Parentheses](https://leetcode.com/problems/remove-invalid-parentheses/)| [Swift](./DFS/RemoveInvalidParentheses.swift)| Hard| ★★★★★★| [Add Binary](https://leetcode.com/problems/add-binary/)| [Swift](./Math/AddBinary.swift)| Easy| ★★★★★| [Two Sum](https://leetcode.com/problems/two-sum/)| [Swift](./Array/TwoSum.swift)| Easy| ★★★★★| -[Bnary Tree Paths](https://leetcode.com/problems/binary-tree-paths/)| [Swift](./Tree/BnaryTreePaths.swift)| Easy| ★★★★| +[Binary Tree Paths](https://leetcode.com/problems/binary-tree-paths/)| [Swift](./Tree/BinaryTreePaths.swift)| Easy| ★★★★| [Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/)| [Swift](./DFS/LetterCombinationsPhoneNumber.swift)| Medium| ★★★★| [Merge k Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/)| [Swift](./LinkedList/MergeKSortedLists.swift)| Hard| ★★★★| [Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/)| [Swift](./LinkedList/ReverseLinkedList.swift)| Easy| ★★★| @@ -661,7 +666,7 @@ | | 260 | [Single Number III](https://leetcode.com/problems/single-number-iii/) | Medium | | | 259 | [3Sum Smaller](https://leetcode.com/problems/3sum-smaller/) ♥ | Medium | | [Swift](./Math/AddDigits.swift) | 258 | [Add Digits](https://leetcode.com/problems/add-digits/) | Easy | -| [Swift](./Tree/BnaryTreePaths.swift) | 257 | [Binary Tree Paths](https://leetcode.com/problems/binary-tree-paths/) | Easy | +| [Swift](./Tree/BinaryTreePaths.swift) | 257 | [Binary Tree Paths](https://leetcode.com/problems/binary-tree-paths/) | Easy | | [Swift](./DP/PaintHouse.swift) | 256 | [Paint House](https://leetcode.com/problems/paint-house/) ♥ | Medium | | | 255 | [Verify Preorder Sequence in Binary Search Tree](https://leetcode.com/problems/verify-preorder-sequence-in-binary-search-tree/) ♥ | Medium | | [Swift](./DFS/FactorCombinations.swift) | 254 | [Factor Combinations](https://leetcode.com/problems/factor-combinations/) ♥ | Medium | @@ -683,7 +688,7 @@ | [Swift](./Array/ProductExceptSelf.swift) | 238 | [Product of Array Except Self](https://leetcode.com/problems/product-of-array-except-self/) | Medium | | | 237 | [Delete Node in a Linked List](https://leetcode.com/problems/delete-node-in-a-linked-list/) | Easy | | | 236 | [Lowest Common Ancestor of a Binary Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/) | Medium | -| | 235 | [Lowest Common Ancestor of a Binary Search Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/) | Easy | +| [Swift](./Tree/LowestCommonAncestorBinarySearchTree.swift) | 235 | [Lowest Common Ancestor of a Binary Search Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/) | Easy | | [Swift](./LinkedList/PalindromeLinkedList.swift) | 234 | [Palindrome Linked List](https://leetcode.com/problems/palindrome-linked-list/) | Easy | | | 233 | [Number of Digit One](https://leetcode.com/problems/number-of-digit-one/) | Hard | | [Swift](./Queue/ImplementQueueUsingStacks.swift) | 232 | [Implement Queue using Stacks](https://leetcode.com/problems/implement-queue-using-stacks/) | Easy | @@ -877,7 +882,7 @@ | [Swift](./String/StrStr.swift) | 28 | [Implement strStr()](https://oj.leetcode.com/problems/implement-strstr/) | Easy | | [Swfit](./Array/RemoveElement.swift) | 27 | [Remove Element](https://oj.leetcode.com/problems/remove-element/) | Easy | | [Swift](./Array/RemoveDuplicatesFromSortedArray.swift) | 26 | [Remove Duplicates from Sorted Array](https://oj.leetcode.com/problems/remove-duplicates-from-sorted-array/) | Easy | -| | 25 | [Reverse Nodes in k-Group](https://oj.leetcode.com/problems/reverse-nodes-in-k-group/) | Hard | +| [Swift](./LinkedList/ReverseNodesInKGroup.swift) | 25 | [Reverse Nodes in k-Group](https://oj.leetcode.com/problems/reverse-nodes-in-k-group/) | Hard | | [Swift](./LinkedList/SwapNodesInPairs.swift) | 24 | [Swap Nodes in Pairs](https://oj.leetcode.com/problems/swap-nodes-in-pairs/) | Easy | | [Swift](./LinkedList/MergeKSortedLists.swift) | 23 | [Merge k Sorted Lists](https://oj.leetcode.com/problems/merge-k-sorted-lists/) | Hard | | [Swift](./Math/GenerateParentheses.swift) | 22 | [Generate Parentheses](https://oj.leetcode.com/problems/generate-parentheses/) | Medium | diff --git a/SlidingWindow/FrequencyMostFrequentElement.swift b/SlidingWindow/FrequencyMostFrequentElement.swift new file mode 100644 index 00000000..62d3ab09 --- /dev/null +++ b/SlidingWindow/FrequencyMostFrequentElement.swift @@ -0,0 +1,27 @@ +/** + * Question Link: https://leetcode.com/problems/frequency-of-the-most-frequent-element/ + * Primary idea: Slding window, sort the nums and move left if k cannot make current window even. + * + * Time Complexity: O(nlogn), Space Complexity: O(1) + * + */ + +class FrequencyMostFrequentElement { + func maxFrequency(_ nums: [Int], _ k: Int) -> 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/SlidingWindow/SubarraysKDifferentIntegers.swift b/SlidingWindow/SubarraysKDifferentIntegers.swift new file mode 100644 index 00000000..e003a600 --- /dev/null +++ b/SlidingWindow/SubarraysKDifferentIntegers.swift @@ -0,0 +1,59 @@ +/** + * Question Link: https://leetcode.com/problems/subarrays-with-k-different-integers/ + * Primary idea: Sliding window. + * Assuming i1 < i2 < i3, (i1,j) and (i3,j) are valid, (i2,j) is definitely valid. + * However, assuming i3 < i4, (i4,j) is is only invalid when the distance between i4 and j is less than k. + * Using 2 sliding windows could solve the problem. + * + * Time Complexity: O(n), Space Complexity: O(n) + * + */ + +class SubarraysKDifferentIntegers { + func subarraysWithKDistinct(_ nums: [Int], _ k: Int) -> Int { + var left1 = 0, left2 = 0, window1 = Window(), window2 = Window(), uniqueSubarrayCount = 0 + + for right in 0.. k { + window1.remove(nums[left1]) + left1 += 1 + } + + while window2.uniqueNum >= k { + window2.remove(nums[left2]) + left2 += 1 + } + + uniqueSubarrayCount += (left2 - left1) + } + + return uniqueSubarrayCount + } + + class Window { + var uniqueNum = 0 + private var numFreq = [Int: Int]() + + func add(_ num: Int) { + numFreq[num, default: 0] += 1 + if numFreq[num] == 1 { + uniqueNum += 1 + } + } + + func remove(_ num: Int) { + if let freq = numFreq[num] { + numFreq[num] = freq - 1 + + if freq == 1 { + uniqueNum -= 1 + } + } + } + } +} + 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/Sort/SortColors.swift b/Sort/SortColors.swift index 89beee51..cf2ec298 100644 --- a/Sort/SortColors.swift +++ b/Sort/SortColors.swift @@ -6,23 +6,25 @@ class SortColors { func sortColors(_ nums: inout [Int]) { - var red = 0, blue = nums.count - 1, i = 0 + var redIdx = 0, blueIdx = nums.count - 1, currentIdx = 0 - while i <= blue { - if nums[i] == 0 { - _swap(&nums, i, red) - red += 1 - i += 1 - } else if nums[i] == 1 { - i += 1 - } else { - _swap(&nums, i, blue) - blue -= 1 + while currentIdx <= blueIdx { + let num = nums[currentIdx] + + if num == 0 { + swap(&nums, redIdx, currentIdx) + redIdx += 1 + } else if num == 2 { + swap(&nums, currentIdx, blueIdx) + blueIdx -= 1 + currentIdx -= 1 } + + currentIdx += 1 } } - fileprivate func _swap(_ nums: inout [T], _ p: Int, _ q: Int) { - (nums[p], nums[q]) = (nums[q], nums[p]) + private func swap(_ nums: inout [Int], _ left: Int, _ right: Int) { + (nums[left], nums[right]) = (nums[right], nums[left]) } -} \ No newline at end of file +} 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/LicenseKeyFormatting.swift b/String/LicenseKeyFormatting.swift new file mode 100644 index 00000000..d7502269 --- /dev/null +++ b/String/LicenseKeyFormatting.swift @@ -0,0 +1,35 @@ +/** + * Question Link: https://leetcode.com/problems/license-key-formatting/ + * Primary idea: traverse the array formatted string from end. Insert the dash into the String as per given K, else insert the elements at index 0. + * + * Time Complexity: O(n), Space Complexity: O(1) + * + */ + +class LicenseKeyFormatting { + func licenseKeyFormatting(_ S: String, _ K: Int) -> String { + guard S.count > 0 && K > 0 else{ + return "" + } + + let upperCaseString = Array(S.uppercased()) + var result = [Character](), n = K + + for i in stride(from: S.count - 1, through: 0, by: -1){ + let aCharacter = upperCaseString[i] + guard aCharacter != "-" else{ + continue + } + + if n == 0{ + result.insert("-", at: 0) + n = K + } + + result.insert(aCharacter, at: 0) + n -= 1 + } + + return String(result) + } +} 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/MinimumStepsMakeTwoStringsAnagram.swift b/String/MinimumStepsMakeTwoStringsAnagram.swift new file mode 100644 index 00000000..68ff5ede --- /dev/null +++ b/String/MinimumStepsMakeTwoStringsAnagram.swift @@ -0,0 +1,17 @@ +/** + * Question Link: https://leetcode.com/problems/minimum-number-of-steps-to-make-two-strings-anagram/ + * Primary idea: Check character frequency in the original string and replace all redundant ones. + * Time Complexity: O(n), Space Complexity: O(n) + */ + +class MinimumStepsMakeTwoStringsAnagram { + func minSteps(_ s: String, _ t: String) -> Int { + var tFreq = Dictionary(t.map { ($0, 1) }, uniquingKeysWith: +) + + for char in s { + tFreq[char, default: 0] -= 1 + } + + return tFreq.values.filter { $0 > 0 }.reduce(0) { $0 + $1 } + } +} 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/ShortestWayFormString.swift b/String/ShortestWayFormString.swift new file mode 100644 index 00000000..82dba6b5 --- /dev/null +++ b/String/ShortestWayFormString.swift @@ -0,0 +1,30 @@ +/** + * Question Link: https://leetcode.com/problems/shortest-way-to-form-string/ + * Primary idea: Iterate through source and consume same characters for the target + * Time Complexity: O(n), Space Complexity: O(1) + */ + +class ShortestWayFormString { + func shortestWay(_ source: String, _ target: String) -> Int { + var res = 0, idx = 0 + let s = Array(source), t = Array(target) + + while idx < t.count { + let pre = idx + + for i in 0.. Bool { + var ACount = 0, LCount = 0 + let sChars = Array(s) + + for i in 0..= 2 { + return false + } + } else if sChars[i] == "L" { + if i > 0 && sChars[i - 1] == "L" { + LCount += 1 + + if LCount >= 3 { + return false + } + } else { + LCount = 1 + } + } + } + + return true + } +} \ No newline at end of file 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/String/ZigZagConverstion.swift b/String/ZigzagConversion.swift similarity index 97% rename from String/ZigZagConverstion.swift rename to String/ZigzagConversion.swift index 3503aba5..cbde9d45 100644 --- a/String/ZigZagConverstion.swift +++ b/String/ZigzagConversion.swift @@ -8,7 +8,7 @@ * */ -class Solution { +class ZigzagConversion { func convert(s: String, _ numRows: Int) -> String { if numRows == 1 { return s 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 "" + } +}