├── .DS_Store ├── Array and String ├── .DS_Store ├── Array and String Problems Part #1.md ├── Array and String Problems Part #2.md └── Array and String.md ├── BFS └── BFS Tutorial.md ├── BST ├── .DS_Store ├── BST Problems Part #1.md └── BST Tutorial.md ├── Backtracking ├── Backtracking Problems Part #1.md └── Backtracking Tutorials.md ├── Basic LeetCode Template.md ├── Binary Search ├── .DS_Store ├── Binary Search Problems Part #1.md └── Binary Search Tutorials.md ├── Binary Tree ├── .DS_Store ├── Binary Tree Problems Part #1.md └── Binary Tree Tutorial.md ├── Bit Manipulation └── Bit Problems Part #1.md ├── Bit └── Bit Manipulation Part #1.md ├── Common-method-cheet-sheet.md ├── DFS ├── DFS Problems Part #1.md └── DFS Tutorial.md ├── Dynamic Programming ├── .DS_Store ├── Dynamic Programming Problems Part #1.md └── Dynamic Programming Tutorials.md ├── Graph ├── .DS_Store ├── Graph Part #1.md ├── Other Graph.md └── Union Find Tutorial.md ├── Greedy └── Greedy Algorithm Part #1.md ├── Hash Table ├── .DS_Store ├── Hash Map Problems Part #1.md ├── Hash Map Problems Part #2.md ├── Hash Set Problems Part #1.md └── Hash Table Basic Tutorial.md ├── Heap ├── .DS_Store ├── Heap Problems Part #1.md └── Heap Tutorials.md ├── LeeCode Summary.xlsx.xlsx ├── LinkedList ├── LinkedList Problems Part #1.md └── LinkedList Tutorial.md ├── Math ├── .DS_Store └── Math Problems Part #1.md ├── OA Summary └── Online Assessment.md ├── Queue & Stack ├── Queue Problems Part #1.md └── Stack Problems Part #1.md ├── README.md ├── Recursion ├── .DS_Store ├── Recursion Problems Part #1.md ├── Recursion Tutorial II.md └── Recursion Tutorial.md ├── SQL ├── .DS_Store └── SQL Problems Part #1.md ├── Sort └── Sorting Problems Part #1.md ├── System Design ├── System Design Problem Part #1.md └── System Design Tutorial.md ├── Trie └── Trie Tutorial.md ├── Two Pointers ├── .DS_Store └── Two Pointers Problems Part #1.md └── ~$LeeCode Summary.xlsx.xlsx /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/.DS_Store -------------------------------------------------------------------------------- /Array and String/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Array and String/.DS_Store -------------------------------------------------------------------------------- /BST/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/BST/.DS_Store -------------------------------------------------------------------------------- /BST/BST Problems Part #1.md: -------------------------------------------------------------------------------- 1 | BST Problems Part #1 2 | 3 | ## Two Sum IV(Easy #653) 4 | 5 | **Question**: Given the `root` of a Binary Search Tree and a target number `k`, return *`true` if there exist two elements in the BST such that their sum is equal to the given target*. 6 | 7 | **Example 1:** 8 | 9 | img 10 | 11 | ``` 12 | Input: root = [5,3,6,2,4,null,7], k = 9 13 | Output: true 14 | ``` 15 | 16 | **Example 2:** 17 | 18 | img 19 | 20 | ``` 21 | Input: root = [5,3,6,2,4,null,7], k = 28 22 | Output: false 23 | ``` 24 | 25 | **Constraints:** 26 | 27 | - The number of nodes in the tree is in the range `[1, 104]`. 28 | - `-104 <= Node.val <= 104` 29 | - `root` is guaranteed to be a **valid** binary search tree. 30 | - `-105 <= k <= 105` 31 | 32 | ### Standard Solution 33 | 34 | #### Solution #1 Using HashSet 35 | 36 | * Simplest solution is to traverse over the whole tree and consider every pair 37 | * For every node, check if the complement is in the set. Then put it to the set. 38 | 39 | ```java 40 | public boolean findTarget(TreeNode root, int k){ 41 | Set set = new HashSet(); 42 | return find(root, k, set); 43 | } 44 | 45 | public boolean find(TreeNode root, int k, Set set){ 46 | if (root == null) return false; 47 | if (set.contains(k - root.val)) return true; 48 | set.add(root.val); 49 | return find(root.left, k, set) || find(root.right, k, set);//find left or right 50 | } 51 | ``` 52 | 53 | * Time complexity : $O(n)$. The entire tree is traversed only once in the worst case. Here, n refers to the number of nodes in the given tree. 54 | * Space complexity : $O(n)$. The size of the set can grow up to n in the worst case. 55 | 56 | #### Solution #2 BFS and HashSet 57 | 58 | * BFS: **Breadth First Search** (traversal) 59 | * **Algorithm**: 60 | * Remove an element, $p$, from the front of the **queue**. 61 | * Check if the element $k-p$ already exists in the set. If so, return True. 62 | * Otherwise, add this element, $p$ to the set. Further, add the right and the left child nodes of the current node to the back of the **queue**. 63 | * Continue steps 1. to 3. till the **queue** becomes empty. 64 | * Return false if the **queue** becomes empty. 65 | 66 | ```java 67 | public boolean findTarget(TreeNode root, int k){ 68 | Set set = new HashSet(); 69 | Queue queue = new LinkedList();//only add, linkedlist is faster 70 | queue.add(root); 71 | while(!queue.isEmpty()){ 72 | if (queue.peek() != null){ 73 | TreeNode node = queue.remove(); 74 | if (set.contains(k - node.val)) return true; 75 | set.add(node.val); 76 | queue.add(node.right);//put right to the last part of the queue 77 | queue.add(node.left);//put left to the front of the queue 78 | } else queue.remove(); 79 | } 80 | return false; 81 | } 82 | ``` 83 | 84 | * Time complexity : $O(n)$. We need to traverse over the whole tree once in the worst case. Here, n refers to the number of nodes in the given tree. 85 | * Space complexity : $O(n)$. The size of the set can grow at most up to n. 86 | 87 | #### Solution #3 Using BST + Two Pointers 88 | 89 | * Using BST, in order BST print as a sorted array 90 | * Get the sorted array, apply two pointers method to find the target 91 | 92 | ```java 93 | public boolean findTarget(TreeNode root, int k){ 94 | List list = new ArrayList();//create arraylist to contain the sorted array 95 | inorder(root, list); 96 | int l = 0, r = list.size() - 1; 97 | while(l < r){ 98 | int sum = list.get(l) + list.get(r); 99 | if (sum == k)return true; 100 | else if (sum < k) l++; 101 | else r--; 102 | } 103 | return false; 104 | } 105 | 106 | public void inorder(TreeNode root, List list){ 107 | if (root == null) return; 108 | inorder(root.left, list);//go left 109 | list.add(root.val);//operation 110 | inorder(root.right, list);//then go right 111 | } 112 | ``` 113 | 114 | * Time complexity : $O(n)$. We need to traverse over the whole tree once to do them in-order traversal. Here, n refers to the number of nodes in the given tree. 115 | * Space complexity : $O(n)$. The sorted list will contain n elements. 116 | 117 | ## Kth Smallest Element in a BST (Medium #230) 118 | 119 | **Question**: Given the `root` of a binary search tree, and an integer `k`, return *the* `kth` *smallest value (**1-indexed**) of all the values of the nodes in the tree*. 120 | 121 | **Example 1:** 122 | 123 | ![img](https://assets.leetcode.com/uploads/2021/01/28/kthtree1.jpg) 124 | 125 | ``` 126 | Input: root = [3,1,4,null,2], k = 1 127 | Output: 1 128 | ``` 129 | 130 | **Example 2:** 131 | 132 | ![img](https://assets.leetcode.com/uploads/2021/01/28/kthtree2.jpg) 133 | 134 | ``` 135 | Input: root = [5,3,6,2,4,null,null,1], k = 3 136 | Output: 3 137 | ``` 138 | 139 | **Constraints:** 140 | 141 | - The number of nodes in the tree is `n`. 142 | - `1 <= k <= n <= 104` 143 | - `0 <= Node.val <= 104` 144 | 145 | **Follow up:** If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize? 146 | 147 | ### My Solution 148 | 149 | ```java 150 | public int kthSmallest(TreeNode root, int k) { 151 | // 1. traverse the tree and put values in a list 152 | List treeList = new ArrayList<>(); 153 | 154 | inorder(root, treeList); 155 | // 2. get the corresponding value in kth location 156 | return treeList.get(k - 1); 157 | 158 | } 159 | 160 | public void inorder(TreeNode root, List treeList){ 161 | if (root == null) return; 162 | inorder(root.left, treeList); 163 | treeList.add(root.val); 164 | inorder(root.right, treeList); 165 | } 166 | ``` 167 | 168 | ### Standard Solution 169 | 170 | #### Solution #1 Recursive Inorder Traversal 171 | 172 | * Same as my solution 173 | 174 | - Time complexity: $O(N)$ to build a traversal. 175 | - Space complexity: $O(N)$ to keep an inorder traversal. 176 | 177 | #### Solution #2 Iterative Inorder Traversal 178 | 179 | * With the help of a stack to do the iterative traversal, speed up the solution. 180 | * One could stop after the kth element. 181 | 182 | ```java 183 | public int kthSmallest(TreeNode root, int k){ 184 | LinkedList stack = new LinkedList<>(); 185 | 186 | while(true){ 187 | while(root != null){ 188 | stack.push(root); 189 | root = root.left; 190 | } 191 | root = stack.pop(); 192 | if (--k == 0) return root.val; 193 | root = root.right; 194 | } 195 | } 196 | ``` 197 | 198 | - Time complexity: $O(H + k)$, where H is a tree height. This complexity is defined by the stack, which contains at least. $H + k$ elements since before starting to pop out, one has to go down to a leaf. This results in $O(\log N + k)$ for the balanced tree and $O(N + k)$ for the completely unbalanced tree with all the nodes in the left subtree. 199 | - Space complexity: $O(H)$ to keep the stack, where H is a tree height. That makes $O(N)$ in the worst case of the skewed tree and $O(\log N)$ in the average case of the balanced tree. 200 | -------------------------------------------------------------------------------- /Basic LeetCode Template.md: -------------------------------------------------------------------------------- 1 | # LeetCode Template 一页通 2 | 3 | ## Binary Search 4 | 5 | **Complexity** 6 | 7 | * Time complexity: $O(\log N)$ - 因为每次都从中间开始把数据split成一半一半,log的底数为2 8 | * Space complexity: $O(1)$ if not adding other structure 9 | * 通常使用constant space来存储mid以及需要return的结果 10 | 11 | 12 | #### **Template 1** 13 | 14 | ```java 15 | int binarySearch(int[] nums, int target){ 16 | if(nums == null || nums.length == 0) 17 | return -1; 18 | 19 | int left = 0, right = nums.length - 1; 20 | while(left <= right){ 21 | // Prevent (left + right) overflow 22 | int mid = left + (right - left) / 2; 23 | if(nums[mid] == target){ return mid; } 24 | else if(nums[mid] < target) { left = mid + 1; } 25 | else { right = mid - 1; } 26 | } 27 | 28 | // End Condition: left > right 29 | return -1; 30 | } 31 | ``` 32 | 33 | #### **Template 2** 34 | 35 | ```java 36 | int binarySearch(int[] nums, int target){ 37 | if(nums == null || nums.length == 0) 38 | return -1; 39 | 40 | int left = 0, right = nums.length; 41 | while(left < right){ 42 | // Prevent (left + right) overflow 43 | int mid = left + (right - left) / 2; 44 | if(nums[mid] == target){ return mid; } 45 | else if(nums[mid] < target) { left = mid + 1; } 46 | else { right = mid; } 47 | } 48 | 49 | // Post-processing: 50 | // End Condition: left == right 51 | if(left != nums.length && nums[left] == target) return left; 52 | return -1; 53 | } 54 | ``` 55 | 56 | #### **Template 3** 57 | 58 | ```java 59 | int binarySearch(int[] nums, int target) { 60 | if (nums == null || nums.length == 0) 61 | return -1; 62 | 63 | int left = 0, right = nums.length - 1; 64 | while (left + 1 < right){ 65 | // Prevent (left + right) overflow 66 | int mid = left + (right - left) / 2; 67 | if (nums[mid] == target) { 68 | return mid; 69 | } else if (nums[mid] < target) { 70 | left = mid; 71 | } else { 72 | right = mid; 73 | } 74 | } 75 | 76 | // Post-processing: 77 | // End Condition: left + 1 == right 78 | if(nums[left] == target) return left; 79 | if(nums[right] == target) return right; 80 | return -1; 81 | } 82 | ``` 83 | 84 | ## Dynamic Programming 85 | 86 | * **Top-down**: Recursion + Memoization(usually hashmap) 87 | * **Bottom-up**: Create an array to cumulatively store values or flags 88 | 89 | * 通常提问方式: 90 | * Maximum/minimum problems 91 | * How many ways 92 | * The result depends on optimal sub-problems solutions 93 | * **Process**: 94 | * 分析State variables的数量决定了array的dimension: what decisions are there to make, what information is needed 95 | * 如何进行递归: The recurrence relation to transition between states 96 | * Base cases 97 | 98 | ### Memoization(Top-down) + recursion 99 | 100 | * 使用array来记录每个sub-problem的cumulative的结果,储存的array最好是given条件的array 101 | * 在given array上进行修改,可以保持$O(1)$的space complexity 102 | * 该方法绑定recursion,且通常使用一个helper method来完成recursion 103 | * Base case 104 | * Recurrence relationship 105 | * Time complexity: $O(n)$ 106 | * 因为我们使用了hashmap进行临时结果的存储,因此每次recursion的时候,对于已存储的结果是$O(1)$ 107 | * 不过因为可能在决定action的时候,会从不同的可能性出发,因此这个$n$也可能是$2n$, $3n$,但总体converge to $n$ 108 | * 值得注意的是,由于需要进行大量的recursion,尽管已经有hashmap来大大缩短时间,但与其他的dp模板相比较,总体运行时间并不会很快 109 | * Space complexity: $O(n)$ 110 | * 存储hashmap所需要的空间 111 | 112 | #### **Template 1 - HashMap** 113 | 114 | ```java 115 | // step 1: 创建一个hashmap,用以记录对应的结果 116 | private HashMap memo = new HashMap<>(); 117 | // 公开given variables,因为helper method也需要用 118 | 119 | public int mainFunct(int[][] costs){ 120 | // 链接真正用来dp的helper method 121 | return Something; 122 | } 123 | 124 | // step2: 考虑具体有多少变量: 125 | // 1. state variables - 有多少个状态(日期, holding, color..),就有多少个dimension需要考虑 126 | // 2. actions - 可能产生的动作,以股票题为例(买入,售出等), 以及该动作应何时产生 127 | 128 | // step3: 创建一个dp的method,输入两个state用以进行recurrsion 129 | public int dp(state1, state2){ 130 | // sub-step 1: 当变量触底,base cases 131 | if (day == array.length){ 132 | return 0; 133 | } 134 | // sub-step 2: 检查是否已经存储了对应的数据 135 | if (memo.containsKey(state1)){ 136 | return memo.get(state1)); 137 | } 138 | // sub-step 3: recurrence relationship 链接下一个index,以及找到sub-problem的最优 139 | currentstate = dp(state1 + 1, state2); 140 | // sub-step 4: 记录进入memo hashmap,返回该数值 141 | memo.put(getKey(state1), totalCost); 142 | return totalCost; 143 | } 144 | ``` 145 | 146 | **Example** 147 | 148 | ```java 149 | // 以 paint house 题目为例 150 | 151 | // step 1: 创建一个hashmap,用以记录对应的结果 152 | private HashMap memo = new HashMap<>(); 153 | // 通常把given array先存起来,因为memoization通常需要另外再造方法 154 | private int[][] costs; 155 | 156 | public int mainFunct(int[][] costs){ 157 | this.costs = costs; 158 | // 链接真正用来dp的helper method 159 | return Something // Math.min(Math.min(paintCost(0, 0), paintCost(0, 1)), paintCost(0, 2)); 160 | } 161 | 162 | // step2: 考虑具体有多少变量: 163 | // 1. state variables - 有多少个状态(日期, holding, color..),就有多少个dimension需要考虑 164 | // 2. actions - 可能产生的动作,以股票题为例(买入,售出等), 以及该动作应何时产生 165 | 166 | // step3: 创建一个dp的method,输入两个state用以进行recurrsion 167 | public int paintCost(int day, int color){ 168 | // Memoization template: *************************************************************** 169 | // sub-step 1: 当变量触底,base cases 170 | if (day == costs.length){ 171 | return 0; 172 | } 173 | // sub-step 2: 检查是否已经存储了对应的数据 174 | if (memo.containsKey(getKey(day, color))){ 175 | return memo.get(getKey(day, color)); 176 | } 177 | // sub-step 3: recurrence relationship 链接下一个index,以及找到sub-problem的最优 178 | // the cost at current day and color 179 | int totalCost = costs[day][color]; 180 | if (color == 0){ 181 | // red color 182 | totalCost += Math.min(paintCost(day + 1, 1), paintCost(day + 1, 2)); 183 | } 184 | else if (color == 1){ 185 | // blue color 186 | totalCost += Math.min(paintCost(day + 1, 0), paintCost(day + 1, 2)); 187 | } 188 | else { 189 | // green color 190 | totalCost += Math.min(paintCost(day + 1, 0), paintCost(day + 1, 1)); 191 | } 192 | // sub-step 4: 记录进入memo hashmap,返回该数值 193 | memo.put(getKey(day, color), totalCost); 194 | return totalCost; 195 | } 196 | ``` 197 | 198 | #### Template 2 - 多元或单元Array 199 | 200 | * 具体的思考过程与hashmap近似,但是使用了array来进行数据的存储 201 | * 适用于需要考虑多个状态变量的同时,某些变量(如日期等)并不需要在每个节点进行单一的动作 202 | * 以股票为例,动作分别有售出和hold两种动作/购买和hold,但是hashmap通常只有一个动作 203 | * 当涉及到某个状态的记录而非单纯数值的记录时,array会比hashmap更加通用,不过array的dimension会更高 204 | * 以股票为例,一个状态变量即为hold/unhold,既此时是否持有股票 205 | * Time and space complexity : $O(m*n)$, 分别都是多元array的长度 206 | 207 | ```java 208 | // step 1: 考虑具体有多少变量: 209 | // 1. state variables - 有多少个状态(日期, holding, color..),就有多少个dimension需要考虑 210 | // 2. actions - 可能产生的动作,以股票题为例(买入,售出等), 以及该动作应何时产生 211 | 212 | // step 2: 创建一个array,用以记录对应的结果 213 | private int[][] memo; 214 | // 公开given variables,因为helper method也需要用 215 | 216 | public int mainFunct(int[][] costs){ 217 | memo = new int[prices.length + 1][2]; // 创建对应的memo array,通常对于given变量长度 + 1, 存储结果 218 | // 链接真正用来dp的helper method 219 | return Something; 220 | } 221 | 222 | // step3: 创建一个dp的method,输入两个state用以进行recurrsion 223 | public int dp(state1, state2){ 224 | // sub-step 1: 当变量触底,base cases 225 | if (day == array.length){ 226 | return 0; 227 | } 228 | // sub-step 2: 检查是否已经存储了对应的数据,但是检查的是array的内容 229 | if (memo[state1][state2] == 0){ 230 | // sub-step 3: 分辨可能出现的动作,recurrence relationship 链接下一个index,以及找到sub-problem的最优 231 | int doNothing = dp(state1 + 1, state2); // 动作1 - 结果1 232 | int doSomething; // 动作2 - 结果2 233 | 234 | if (holding == 1){ 235 | // doSomething, action 1 236 | } 237 | else { 238 | // doSomething, action 2 239 | } 240 | // sub-step 4: 对比两种动作的结果,记录进入memo array,返回该数值 241 | memo[state1][state2] = Math.max(doNothing, doSomething); 242 | } 243 | return memo[state1][state2]; 244 | } 245 | ``` 246 | 247 | **Example 以股票问题为例** 248 | 249 | ```java 250 | // step 1: 考虑具体有多少变量: 251 | // 1. state variables - 有多少个状态(日期, holding, color..),就有多少个dimension需要考虑 252 | // 2. actions - 可能产生的动作,以股票题为例(买入,售出等), 以及该动作应何时产生 253 | 254 | // step 2: 创建一个memo array,用以记录对应的结果, dimension = # state variables 255 | private int[] prices; 256 | private int[][] memo; 257 | private int fee; 258 | 259 | public int mainFunct(int[][] costs){ 260 | this.prices = prices; 261 | this.fee = fee; 262 | // 该题的考量: 263 | // two state variables: day, holding stock or not 264 | // two actions: buy, sell 265 | 266 | memo = new int[prices.length + 1][2]; // 创建对应的memo array,通常对于given变量长度 + 1 267 | return dp(0, 0); 268 | } 269 | 270 | // step3: 创建一个dp的method,输入两个state用以进行recurrsion 271 | public int dp(int day, int holding){ 272 | // sub-step 1: 当变量触底,base cases 273 | if (day == prices.length){ 274 | return 0; 275 | } 276 | // sub-step 2: 检查是否已经存储了对应的数据,但是检查的是array的内容 277 | if (memo[day][holding] == 0){ 278 | // sub-step3: 分辨可能出现的动作,recurrence relationship 链接下一个index,以及找到sub-problem的最优 279 | int doNothing = dp(day + 1, holding); // 动作1 - 结果1 280 | int doSomething; // 动作2 - 结果2 281 | 282 | // do something we have two actions 283 | // 1 - holding stock, 0 - does not hold 284 | if (holding == 1){ 285 | // sell the stock? 286 | doSomething = prices[day] - fee + dp(day + 1, 0); 287 | } 288 | else { 289 | // buy a stock? 290 | doSomething = -prices[day] + dp(day + 1, 1); 291 | } 292 | // sub-step 4: 记录进入memo array,返回该数值 293 | memo[day][holding] = Math.max(doNothing, doSomething); 294 | } 295 | return memo[day][holding]; 296 | } 297 | ``` 298 | 299 | ### Bottom-up + No Recursion 300 | 301 | * 通常由top-down的方法转化,但是不需要进行recursion因此运行速度上快很多 302 | * 通常需要进行for loop 303 | * 视情况可以控制space complexity到$O(1)$的水平,是dp中较为好的方法,但是可能比较难想到 304 | 305 | #### Template 1 - Changing States 306 | 307 | * 使用一些state varibles来存储变量,在循环中不断更改value,通常需要两个或以上 308 | * 通常使用`Math.min()`或者`Math.max()`来进行关系上的传递 309 | * 通常时间complexity为$O(n)$, 空间的complexity为$O(1)$ 310 | * **难点**: 如果寻找内部的recurrence relationship,这里可能会很难 311 | 312 | ```java 313 | public int maxProfit(int[] prices){ 314 | // step 1: set up states and initial values, usually 0, min, max 315 | int sold = Integer.MIN_VALUE, held = Integer.MIN_VALUE, reset = 0; 316 | 317 | // step 2: for loop 318 | for (int price : prices){ 319 | int preSold = sold; 320 | // step 3: states recurrence relationship 321 | sold = held + price; 322 | held = Math.max(held, reset - price); 323 | reset = Math.max(reset, preSold); 324 | } 325 | // step 4: return and compare max and min 326 | return Math.max(sold, reset); 327 | } 328 | ``` 329 | 330 | ##### T 1.1 Kanede's Algorithm 331 | 332 | * Goal: find maximum subarray - find the contiguous subarray (containing at least one number) which has the largest sum 333 | 334 | ```java 335 | public int maxSubArray(int[] nums) { 336 | // kadene's algorithm 337 | int current = 0, max = Integer.MIN_VALUE; 338 | for (int num : nums){ 339 | // should we abandon the previous values? 340 | current = Math.max(num, current + num); 341 | max = Math.max(max, current); 342 | } 343 | return max; 344 | } 345 | ``` 346 | 347 | #### Template 2 - Sum-up Array 348 | 349 | * 与memoization的做法相似,但是不需要进行recursion,而是使用for-loop 350 | * 通常时间complexity为$O(n)$, 空间的complexity为$O(1)$ 351 | * 通常使用`Math.min()`或者`Math.max()`来进行关系上的传递 352 | * 在array的最后一行或列中寻找最大或最小 353 | * **难点**: 如果寻找内部的recurrence relationship 354 | 355 | ```java 356 | public int minCost(int[][] costs) { 357 | // step 1: Use for loop 传递并递增信息,例如次数,cost,等 358 | for (int n = costs.length - 2; n >= 0; n--) { 359 | // step 2: 寻找array之中的递增联系以及关系,一直传递到最后一行 360 | // Total cost of painting the nth house red. 361 | costs[n][0] += Math.min(costs[n + 1][1], costs[n + 1][2]); 362 | // Total cost of painting the nth house green. 363 | costs[n][1] += Math.min(costs[n + 1][0], costs[n + 1][2]); 364 | // Total cost of painting the nth house blue. 365 | costs[n][2] += Math.min(costs[n + 1][0], costs[n + 1][1]); 366 | } 367 | if (costs.length == 0) return 0; 368 | // step 3: 在最后一行寻找答案 369 | return Math.min(Math.min(costs[0][0], costs[0][1]), costs[0][2]); 370 | } 371 | ``` 372 | 373 | ## Backtracking 374 | 375 | * 通常提问方式: 376 | 377 | * **All** possible combinations/solutions/permutations,要详细的**所有**结果的情况 378 | 379 | * Return in list,结果为一个list,list中每个结果都是一个list 380 | * 通常需要另一个backtracking的helper method, helper method需要使用recursion 381 | 382 | #### Template 1 - 通用模版 383 | 384 | * 把所有给的variables public 作全局,包括需要return的结果(很可能是list,先做好return的空list) 385 | * 在主方法中调用backtracking,并在其中创建一个空list作为单个的结果 386 | * backtracking: 387 | * 通常下是void method,调用参数index 388 | * index在此处用于追踪我们在given array中的位置,记录是否达到了given array 的末端 389 | * 同时index也是下一个可能结果的先决条件 390 | * 写好base case for recursion: 391 | * 到达了target value 392 | * 到达了given array的末端 393 | * 注意需要重新建一个arraylist来结果当前结果list 394 | * for loop 395 | * 从loop中进行操作链接到下一个可能的结果 396 | * 把本次结果加入到结果集中 397 | * 进入下一个可能的结果 398 | * 把本次结果从结果集中**删除**:达到backtracking的目的 399 | 400 | ```java 401 | // step 1: 把所有给的variables public 作全局,创建res的list(通常list中list嵌套) 402 | List> res; 403 | int[] candidates; 404 | 405 | public List> main(int[] candidates) { 406 | res = new ArrayList<>(); 407 | this.candidates = candidates; 408 | // step 2: 思考需要把什么带入backtracking的方法(target,index等),并套入一个空结果list 409 | backtracking(0, new ArrayList()); 410 | // 直接return已经做好的result list 411 | return res; 412 | } 413 | 414 | // step 2: 思考需要把什么带入backtracking的方法(target,*index*等) 415 | // 并套入一个*空结果list(指代当前结果)* 416 | // 通常是void method 417 | public void backtracking(int start, List singleComb){ 418 | // step 3: base case 刚好达成结果的条件,在此增加当前结果到结果集 419 | if (start == candidates.length){ 420 | res.add(new ArrayList(singleComb)); 421 | return; 422 | } 423 | // step 4: **最重要**,通常需要for-loop来进入下一个可能的结果 ***************** 424 | // 从当前index出发,直到given array的末端,代表某个可能的路径 425 | for (int i = start; i < candidates.length; i++){ 426 | // 加入到当前结果 427 | singleComb.add(candidates[i]); 428 | // 进入下一个可能的结果,注意此处 ********* 429 | // * 可重复的结果则i 430 | // * 不可重复的记过则i + 1 431 | backtracking(i + 1, singleComb); 432 | // 移除出当前结果(实现backtracking) 433 | singleComb.remove(singleComb.size() - 1); 434 | } 435 | } 436 | ``` 437 | 438 | 以combination sum为例 439 | 440 | ```java 441 | // step 1: 把所有给的variables public 作全局,创建res的list(通常list中list嵌套) 442 | List> res; 443 | int[] candidates; 444 | int target; 445 | 446 | public List> combinationSum(int[] candidates, int target) { 447 | res = new ArrayList<>(); 448 | this.candidates = candidates; 449 | this.target = target; 450 | // step 2: 思考需要把什么带入backtracking的方法(target,index等),并套入一个空结果list 451 | backtracking(0, target, new ArrayList()); 452 | // 直接return已经做好的result list 453 | return res; 454 | } 455 | 456 | // step 2: 思考需要把什么带入backtracking的方法(target,*index*等) 457 | // 并套入一个*空结果list(指代当前结果)* 458 | // 通常是void method 459 | public void backtracking(int start, int target, List singleComb){ 460 | // step 3: base case 刚好达成结果的条件,在此增加当前结果到结果集 461 | if (target == 0){ 462 | res.add(new ArrayList(singleComb)); // *重要*:建一个new list来接结果 463 | return; 464 | } 465 | // base case: not meet the requirement 466 | if (target < 0 || start == candidates.length){ 467 | return; 468 | } 469 | // step 4: **最重要**,通常需要for-loop来进入下一个可能的结果 ***************** 470 | // 从当前index出发,直到given array的末端,代表某个可能的路径 471 | for (int i = start; i < candidates.length; i++){ 472 | // 加入到当前结果 473 | singleComb.add(candidates[i]); 474 | // 进入下一个可能的结果 475 | backtracking(i, target - candidates[i], singleComb); 476 | // 移除出当前结果(实现backtracking) 477 | singleComb.remove(singleComb.size() - 1); 478 | } 479 | } 480 | ``` 481 | 482 | #### Template 2 - Permutation(通用模版变形) 483 | 484 | * 适用于可能的情况: 485 | * permutation 486 | * 每个return的结果,并不按照原来given array中element的排序,需要乱序处理 487 | * 本质上讲通用模版中的可能结果的元素增减,改变为元素的swap来换位 488 | * 需要将given array(如果有的话)转换成list,方便使用`Collections.swap()`,否则需要自己建立helper method来实现元素的交换 489 | 490 | 以permutation为例 491 | 492 | ```java 493 | // step 1: 把所有给的variables public 作全局,创建res的list(通常list中list嵌套) 494 | List> res; 495 | List nums; 496 | 497 | public List> permute(int[] nums) { 498 | res = new ArrayList<>(); 499 | this.nums = new ArrayList<>(); 500 | // step 2: array 不方便进行swap且不是list,现转换成list就不需要create new list for single result 501 | for (int num : nums){ 502 | this.nums.add(num); 503 | } 504 | // step 3: 思考需要把什么带入backtracking的方法(target,index等) 505 | backtracking(0); 506 | return res; 507 | } 508 | public void backtracking(int index){ 509 | // step 4: base case 刚好达成结果的条件,在此增加当前结果到结果集 510 | if (index == nums.size()){ 511 | res.add(new ArrayList(nums)); 512 | return; 513 | } 514 | // step 4: **最重要**,通常需要for-loop来进入下一个可能的结果 ***************** 515 | // 从当前index出发,直到given array的末端,代表某个可能的路径,并进行swap 516 | for (int i = index; i < nums.size(); i++){ 517 | Collections.swap(nums, i, index); 518 | //*****注意**************** 519 | // 由于是swap并不按顺序排列结果,因此从index进行关联而不是i,保证swap到每个元素以及自己 520 | backtracking(index + 1); 521 | Collections.swap(nums, i, index); 522 | } 523 | } 524 | ``` 525 | 526 | ## UNION FIND 527 | 528 | * Union-Find is all about remembering the patterns. 529 | * 每次使用这个class来进行cluster,对于每个cluster只需要找ancestor的数据(e.g rank,find) 530 | * Create a class called `UnionFind` 531 | * Initialize a `group` array and a `rank` array. Sometimes you may also need to create a `size` array 532 | * Create `find,` and `union` method 533 | * `find`: Find the ancestor(centroid) of the cluster, 用以进行union时确定存在于某个cluster 534 | * `union`: Union two groups. If true, we union them; if false, they are already in the same cluster 535 | * `rank`: Speeding up the `union` process, basically, calculates the path number of the cluster tree. 536 | * Always attach the cluster with a shorter path to the cluster with a longer path. 537 | * 使用方法为path compression 538 | * Time complexity: time complexity for find/union operation is $\alpha({N})$ (Here, $\alpha({N})$ is the inverse Ackermann function that grows so slowly. Suppose we have $N$ elements to go through, then the time compleXIty would be $O(N \alpha({N}))$ 539 | * Space complexity: just using array so it would be $O(N)$ 540 | 541 | ### Template 1 - 通用模版 542 | 543 | ```java 544 | class UnionFind { 545 | private int[] group; 546 | private int[] rank; 547 | 548 | public UnionFind(int n){ 549 | group = new int[n]; 550 | rank = new int[n]; 551 | for (int i = 0; i < n; i++){ 552 | group[i] = i; // initialization: each node is its own group 553 | rank[i] = 1; // each node has rank 1(path length: 1) 554 | } 555 | } 556 | 557 | public int find(int node){ 558 | if (node != group[node]){ 559 | group[node] = find(group[node]); // recursion find the ancestor 560 | } 561 | return group[node]; 562 | } 563 | 564 | public boolean union(int i, int j){ 565 | // find the ancestor 566 | int root1 = find(i); 567 | int root2 = find(j); 568 | 569 | if (root1 == root2){ 570 | // already in same group 571 | return false; 572 | } 573 | // if they are not in the same group 574 | if (rank[root1] > rank[root2]){ 575 | group[root2] = group[root1]; // attach shorter path cluster to the longer one 576 | } 577 | else if (rank[root2] > rank[root1]){ 578 | group[root1] = group[root2]; 579 | } 580 | else { 581 | group[root1] = group[root2]; 582 | rank[root2]++; // same length, one of the cluster have to be ancestor, rank + 1 583 | } 584 | return true; 585 | } 586 | } 587 | ``` 588 | 589 | * **模版的使用** 590 | * 通常在主method中进行class的实例化与使用,例子如下 591 | 592 | ```java 593 | // map to store the number and index, union-find to group the numbers 594 | Map numMap = new HashMap<>(); 595 | UnionFind uf = new UnionFind(nums.length); 596 | for (int i = 0; i < nums.length; i++){ 597 | int curr = nums[i]; 598 | // in case duplicate 599 | if (numMap.containsKey(curr)) continue; 600 | // if map has consecutive elements for curr element 601 | if (numMap.containsKey(curr - 1)){ 602 | uf.union(i, numMap.get(curr - 1)); 603 | } 604 | if (numMap.containsKey(curr + 1)){ 605 | uf.union(i, numMap.get(curr + 1)); 606 | } 607 | numMap.put(curr, i); 608 | } 609 | ``` 610 | 611 | * `rank`不是必须的,可以分情况使用,如果不需要优化time complexity也可以不用(最好用上) 612 | * 如果需要知道每个cluster的size,也可以使用size来替代rank(同样也是path compression) 613 | 614 | ### Template 2 - With Size 615 | 616 | * Path compression with size 617 | 618 | ```java 619 | class UnionFind { 620 | int representative[]; 621 | int size[]; 622 | 623 | public UnionFind(int size){ 624 | representative = new int[size]; 625 | this.size = new int[size]; 626 | 627 | for (int i = 0; i < size; i++){ 628 | representative[i] = i; 629 | this.size[i] = 1; 630 | } 631 | } 632 | 633 | public int find(int x){ 634 | if (x != representative[x]){ 635 | representative[x] = find(representative[x]); 636 | } 637 | return representative[x]; 638 | } 639 | 640 | public void union(int a, int b){ 641 | int root1 = find(a); 642 | int root2 = find(b); 643 | 644 | if (root1 == root2) return; 645 | if (size[root1] > size[root2]){ 646 | size[root1] += size[root2]; 647 | representative[root2] = representative[root1]; 648 | } 649 | else { 650 | size[root2] += size[root1]; 651 | representative[root1] = representative[root2]; 652 | } 653 | } 654 | } 655 | ``` 656 | 657 | ## DFS 658 | 659 | ### DFS - Line Graph 660 | 661 | * 需要创建`adjList`,可以是list或者map,用以存储相关联的nodes的信息(**important**) 662 | 663 | ## BFS 664 | 665 | * Traverse a tree/graph:通常要使用queue来进行 666 | * Traverse by layer 667 | * while循环,queue不为空 668 | * 事先存储一个node(通常为root),然后在循环中pop(poll) 669 | * 进行操作,接着把node的左子树和右子树存储在queue或者stack中,循环往复直到所有node都traverse完成 670 | * **DFS-stack, BFS-queue** 671 | 672 | * Time Complexity: $O(V + E)$. Here, V represents the number of vertices, and E represents the number of edges. We need to check every vertex and traverse through every edge in the graph. The time complexity is the same as it was for the DFS approach. 673 | * Space Complexity: $O(V)$. Generally, we will check if a vertex has been visited before adding it to the queue, so the queue will use most $O(V)$ space. Keeping track of which vertices have been visited will also require $O(V)$ space. 674 | 675 | ## Two Pointer 676 | 677 | ### Linked List 678 | 679 | * Usually **1 fast pointer, 1 slow pointer**, and different speeds of progress(hare and tortoise) 680 | * A fast pointer can move two steps each time, and a slow pointer move 1 step each time 681 | * **Always examine if the node is null before you call the next field.** 682 | * Getting the next node of a null node will cause the null-pointer error. For example, before we run `fast = fast.next.next`, we need to examine both `fast` and `fast.next` is not null. 683 | * **Carefully define the end conditions of your loop.** 684 | 685 | ```java 686 | // Initialize slow & fast pointers 687 | ListNode slow = head; 688 | ListNode fast = head; 689 | /** 690 | * Change this condition to fit the specific problem. 691 | * Attention: remember to avoid null-pointer error 692 | **/ 693 | while (slow != null && fast != null && fast.next != null) { 694 | slow = slow.next; // move slow pointer one step each time 695 | fast = fast.next.next; // move fast pointer two steps each time 696 | if (slow == fast) { // change this condition to fit specific problem 697 | return true; 698 | } 699 | } 700 | return false; // change return value to fit specific problem 701 | ``` 702 | 703 | * Or two pointers have the same speed but start from a different node (remove the nth node from the end) 704 | * **Complexity Analysis** 705 | * If you only use pointers without any other extra space, the space complexity will be `O(1)`. 706 | * However, it is more difficult to analyze the time complexity. In order to get the answer, we need to analyze `how many times we will run our loop` . In our previous finding cycle example, let's assume that we move the faster pointer 2 steps each time and move the slower pointer 1 step each time. 707 | * If there is no cycle, the fast pointer takes `N/2 times` to reach the end of the linked list, where N is the length of the linked list. 708 | * If there is a cycle, the fast pointer needs `M times` to catch up the slower pointer, where M is the length of the cycle in the list. 709 | * Obviously, M <= N. So we will run the loop `up to N times`. And for each loop, we only need constant time. So, the time complexity of this algorithm is `O(N)` in total. 710 | 711 | ## LinkedList一些技巧 712 | 713 | ### DummyHead/Sentinel Node的使用 714 | 715 | * dummy head/sentinel node/pseudo-head 即为在目前的head之前再加一个node,是一个fake node,数值没有意义 716 | * **作用**: 717 | * 在需要移除某个node,比如说head的时候,会非常方便移除(否则很麻烦) 718 | * 需要对list长度进行计数,但计数从1不从0开始时,计数容易错位,此时dummy head可以补足空位 719 | * 在某些题目中需要记录prev node的指针,此时dummy head可以帮助记录head的prev node 720 | * 一般在使用while loop的时候使用dummy head,recursion的情况下很少使用 721 | -------------------------------------------------------------------------------- /Binary Search/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Binary Search/.DS_Store -------------------------------------------------------------------------------- /Binary Search/Binary Search Problems Part #1.md: -------------------------------------------------------------------------------- 1 | # Binary Search Problems Part #1 2 | 3 | ## Closest Binary Search Tree Value(Easy #270) 4 | 5 | **Question**: Given the `root` of a binary search tree and a `target` value, return *the value in the BST that is closest to the* `target` 6 | 7 | **Example 1:** 8 | 9 | ![img](https://assets.leetcode.com/uploads/2021/03/12/closest1-1-tree.jpg) 10 | 11 | ``` 12 | Input: root = [4,2,5,1,3], target = 3.714286 13 | Output: 4 14 | ``` 15 | 16 | **Example 2:** 17 | 18 | ``` 19 | Input: root = [1], target = 4.428571 20 | Output: 1 21 | ``` 22 | 23 | **Constraints:** 24 | 25 | - The number of nodes in the tree is in the range `[1, 104]`. 26 | - `0 <= Node.val <= 109` 27 | - `-109 <= target <= 109` 28 | 29 | ### My Solution 30 | 31 | * In order recursively find the distance between the value and the target 32 | * Compare the distance and record the root value 33 | 34 | ```java 35 | class Solution { 36 | int value = 0; 37 | public int closestValue(TreeNode root, double target) { 38 | double distance = Double.POSITIVE_INFINITY; 39 | findValue(root, target, distance); 40 | return value; 41 | } 42 | // recursively find the value near target 43 | public double findValue(TreeNode root, double target, double distance){ 44 | if (root == null){ 45 | return distance; 46 | } 47 | distance = findValue(root.left, target, distance); 48 | if ((double)Math.abs(target - root.val) < distance){ 49 | distance = (double)Math.abs(target - root.val); 50 | value = root.val; 51 | } 52 | distance = findValue(root.right, target, distance); 53 | return distance; 54 | } 55 | } 56 | ``` 57 | 58 | ### Standard Solution 59 | 60 | #### Solution #1 Recursive Inorder + Linear Search 61 | 62 | * Recursively use the list to record all the value 63 | * Create a customized comparator for comparison 64 | 65 | ```java 66 | public void inorder(TreeNode root, List nums){ 67 | if (root == null) return; 68 | inorder(root.left, nums); 69 | nums.add(root.val); 70 | inorder(root.right, nums); 71 | } 72 | public int closestValue(TreeNode root, double target){ 73 | List nums = new ArrayList(); 74 | inorder(root, nums); // put all values to the list 75 | return Collections.min(nums, new Comparator(){ 76 | @Override 77 | public int compare(Integer o1, Integer o2){ 78 | return Math.abs(o1 - target) < Math.abs(o2 - target) ? -1 : 1; 79 | } 80 | }); 81 | } 82 | ``` 83 | 84 | - Time complexity: $\mathcal{O}(N)$ because to build in order traversal and then to perform linear search takes linear time. 85 | - Space complexity: $\mathcal{O}(N)$ to keep in order traversal. 86 | 87 | #### Solution #2 Binary Search, O(H) time 88 | 89 | * Use the properties of the binary search tree 90 | 91 | pic 92 | 93 | ```java 94 | public int closestValue(TreeNode root, double target){ 95 | int val, closest = root.val; 96 | while (root != null){ 97 | val = root.val; 98 | closest = Math.abs(val - target) < Math.abs(closest - target) ? val : closest; 99 | root = target < root.val ? root.left : root.right; 100 | } 101 | return closest; 102 | } 103 | ``` 104 | 105 | - Time complexity: $\mathcal{O}(H)$ since here one goes from root down to a leaf. 106 | - Space complexity: $\mathcal{O}(1)$. 107 | 108 | ## Search in a Sorted Array of Unknown Size(Medium #702) 109 | 110 | **Question**: This is an ***interactive problem***. 111 | 112 | You have a sorted array of **unique** elements and an **unknown size**. You do not have an access to the array but you can use the `ArrayReader` interface to access it. You can call `ArrayReader.get(i)` that: 113 | 114 | - returns the value at the `ith` index (**0-indexed**) of the secret array (i.e., `secret[i]`), or 115 | - returns `231 - 1` if the `i` is out of the boundary of the array. 116 | 117 | You are also given an integer `target`. 118 | 119 | Return the index `k` of the hidden array where `secret[k] == target` or return `-1` otherwise. 120 | 121 | You must write an algorithm with `O(log n)` runtime complexity. 122 | 123 | **Example 1:** 124 | 125 | ``` 126 | Input: secret = [-1,0,3,5,9,12], target = 9 127 | Output: 4 128 | Explanation: 9 exists in secret and its index is 4. 129 | ``` 130 | 131 | **Example 2:** 132 | 133 | ``` 134 | Input: secret = [-1,0,3,5,9,12], target = 2 135 | Output: -1 136 | Explanation: 2 does not exist in secret so return -1. 137 | ``` 138 | 139 | **Constraints:** 140 | 141 | - `1 <= secret.length <= 104` 142 | - `-104 <= secret[i], target <= 104` 143 | - `secret` is sorted in a strictly increasing order. 144 | 145 | ### My Solution 146 | 147 | * Binary search method 148 | 149 | ```java 150 | public int search(ArrayReader reader, int target) { 151 | int max = (int)Math.pow(10, 4); 152 | int low = 0; 153 | while (low < max){ 154 | int mid = low + (max - low) / 2; 155 | int value = reader.get(mid); 156 | if (value > target){ 157 | max = mid - 1; 158 | } 159 | else if (value < target){ 160 | low = mid + 1; 161 | } 162 | else { 163 | return mid; 164 | } 165 | } 166 | return reader.get(low) == target ? low : -1; 167 | } 168 | ``` 169 | 170 | ### Standard Solution 171 | 172 | #### Solution #1 Binary Search + Bit Operation 173 | 174 | ```java 175 | class Solution { 176 | public int search(ArrayReader reader, int target) { 177 | if (reader.get(0) == target) return 0; 178 | // search boundaries 179 | int left = 0, right = 1; 180 | while (reader.get(right) < target) { 181 | left = right; 182 | right <<= 1; 183 | } 184 | // binary search 185 | int pivot, num; 186 | while (left <= right) { 187 | pivot = left + ((right - left) >> 1); 188 | num = reader.get(pivot); 189 | 190 | if (num == target) return pivot; 191 | if (num > target) right = pivot - 1; 192 | else left = pivot + 1; 193 | } 194 | // there is no target element 195 | return -1; 196 | } 197 | } 198 | ``` 199 | 200 | - Time complexity: $\mathcal{O}(\log T)$, where T is an index of the target value. 201 | 202 | There are two operations here: to define search boundaries and to perform a binary search. 203 | 204 | Let's first find the number of steps k to set up the boundaries. In the first step, the boundaries are $2^0 .. 2^{0 + 1}$, on the second step $2^1 .. 2^{1 + 1}$, etc. When everything is done, the boundaries are $2^k .. 2^{k + 1}$ and $2^k < T \le 2^{k + 1}$. That means one needs $k = \log T$ steps to set up the boundaries, that means $\mathcal{O}(\log T)$ time complexity. 205 | 206 | Now let's discuss the complexity of the binary search. There are $2^{k + 1} - 2^k = 2^k$ elements in the boundaries, i.e. $2^{\log T} = T2$ elements. Binary search has logarithmic complexity, which results in $\mathcal{O}(\log T)$ time complexity. 207 | 208 | - Space complexity: $\mathcal{O}(1)$ since it's a constant space solution. 209 | 210 | ## Valid Perfect Square(Easy #367) 211 | 212 | **Question**: Given a **positive** integer *num*, write a function that returns True if *num* is a perfect square else False. 213 | 214 | **Follow up:** **Do not** use any built-in library function such as `sqrt`. 215 | 216 | **Example 1:** 217 | 218 | ``` 219 | Input: num = 16 220 | Output: true 221 | ``` 222 | 223 | **Example 2:** 224 | 225 | ``` 226 | Input: num = 14 227 | Output: false 228 | ``` 229 | 230 | **Constraints:** 231 | 232 | - `1 <= num <= 2^31 - 1` 233 | 234 | ### My Solution 235 | 236 | * Use binary search to find mid, check if the square larger or smaller than the target 237 | 238 | ```java 239 | public boolean isPerfectSquare(int num) { 240 | if (num == 0 || num == 1){ 241 | return true; 242 | } 243 | if (num < 0){ 244 | return false; 245 | } 246 | int low = 1, high = num / 2; 247 | while (low < high){ 248 | int mid = low + (high - low) / 2; 249 | if ((long)mid * mid < num){ 250 | low = mid + 1; 251 | } 252 | else if ((long)mid * mid > num){ 253 | high = mid - 1; 254 | } 255 | else { 256 | return true; 257 | } 258 | } 259 | return low * low == num ? true : false; 260 | } 261 | ``` 262 | 263 | ### Standard Solution 264 | 265 | #### Solution #1 Binary Search 266 | 267 | * Almost the same as my solution 268 | 269 | ```java 270 | public boolean isPerfectSquare(int num) { 271 | if (num < 2) { 272 | return true; 273 | } 274 | long left = 2, right = num / 2, x, guessSquared; 275 | while (left <= right) { 276 | x = left + (right - left) / 2; 277 | guessSquared = x * x; 278 | if (guessSquared == num) { 279 | return true; 280 | } 281 | if (guessSquared > num) { 282 | right = x - 1; 283 | } else { 284 | left = x + 1; 285 | } 286 | } 287 | return false; 288 | } 289 | ``` 290 | 291 | * Time complexity: $\mathcal{O}(\log N)$. 292 | * Space complexity: $\mathcal{O}(1)$. 293 | 294 | ## Find Smallest Letter Greater Than Target (Easy #744) 295 | 296 | **Question**: Given a characters array `letters` that is sorted in **non-decreasing** order and a character `target`, return *the smallest character in the array that is larger than* `target`. 297 | 298 | **Note** that the letters wrap around. 299 | 300 | - For example, if `target == 'z'` and `letters == ['a', 'b']`, the answer is `'a'`. 301 | 302 | **Example 1:** 303 | 304 | ``` 305 | Input: letters = ["c","f","j"], target = "a" 306 | Output: "c" 307 | ``` 308 | 309 | **Example 2:** 310 | 311 | ``` 312 | Input: letters = ["c","f","j"], target = "c" 313 | Output: "f" 314 | ``` 315 | 316 | **Example 3:** 317 | 318 | ``` 319 | Input: letters = ["c","f","j"], target = "d" 320 | Output: "f" 321 | ``` 322 | 323 | **Constraints:** 324 | 325 | - `2 <= letters.length <= 104` 326 | - `letters[i]` is a lowercase English letter. 327 | - `letters` are sorted in **non-decreasing** order. 328 | - `letters` contain at least two different characters. 329 | - `target` is a lowercase English letter. 330 | 331 | ### My Solution 332 | 333 | * Calculate the relative distance 334 | 335 | ```java 336 | public char nextGreatestLetter(char[] letters, char target) { 337 | int distance = 0; 338 | int minDistance = Integer.MAX_VALUE; 339 | char res = 'a'; 340 | for (char letter : letters){ 341 | if (letter >= target){ 342 | distance = letter - target; 343 | } 344 | else { 345 | distance = 'z' - target + letter - 'a' + 1; 346 | } 347 | if (distance > 0 && distance < minDistance){ 348 | minDistance = distance; 349 | res = letter; 350 | } 351 | } 352 | return res; 353 | } 354 | ``` 355 | 356 | ### Standard Solution 357 | 358 | #### Solution #1 Linear Scan 359 | 360 | * The question asks for the letter, not distance, don't need to calculate the distance 361 | * Just loop through the letters and find the one that meets the requirement 362 | 363 | ```java 364 | class Solution { 365 | public char nextGreatestLetter(char[] letters, char target) { 366 | for (char c: letters) 367 | if (c > target) return c; 368 | return letters[0]; 369 | } 370 | } 371 | ``` 372 | 373 | - Time Complexity: $O(N)$, where N is the length of `letters`. We scan every element of the array. 374 | - Space Complexity: $O(1)$, as we maintain only pointers. 375 | 376 | #### Solution #2 Binary Search 377 | 378 | * Binary search method to check if the letter is larger than the target 379 | 380 | ```java 381 | public char nextGreatestLetter(char[] letters, char target) { 382 | int lo = 0, hi = letters.length; 383 | while (lo < hi) { 384 | int mi = lo + (hi - lo) / 2; 385 | if (letters[mi] <= target) lo = mi + 1; 386 | else hi = mi; 387 | } 388 | return letters[lo % letters.length]; 389 | } 390 | ``` 391 | 392 | - Time Complexity: $O(\log N)$, where N is the length of `letters`. We peek only at $\log N$ elements in the array. 393 | - Space Complexity: $O(1)$, as we maintain only pointers. 394 | 395 | ## Find Minimum in Rotated Sorted Array II (Hard #154) 396 | 397 | **Question**: Suppose an array of length `n` sorted in ascending order is **rotated** between `1` and `n` times. For example, the array `nums = [0,1,4,4,5,6,7]` might become: 398 | 399 | - `[4,5,6,7,0,1,4]` if it was rotated `4` times. 400 | - `[0,1,4,4,5,6,7]` if it was rotated `7` times. 401 | 402 | Notice that **rotating** an array `[a[0], a[1], a[2], ..., a[n-1]]` 1 time results in the array `[a[n-1], a[0], a[1], a[2], ..., a[n-2]]`. 403 | 404 | Given the sorted rotated array `nums` that may contain **duplicates**, return *the minimum element of this array*. 405 | 406 | You must decrease the overall operation steps as much as possible. 407 | 408 | **Example 1:** 409 | 410 | ``` 411 | Input: nums = [1,3,5] 412 | Output: 1 413 | ``` 414 | 415 | **Example 2:** 416 | 417 | ``` 418 | Input: nums = [2,2,2,0,1] 419 | Output: 0 420 | ``` 421 | 422 | **Constraints:** 423 | 424 | - `n == nums.length` 425 | - `1 <= n <= 5000` 426 | - `-5000 <= nums[i] <= 5000` 427 | - `nums` is sorted and rotated between `1` and `n` times. 428 | 429 | ### Standard Solution 430 | 431 | #### Solution #1 Variant of Binary Search 432 | 433 | * Compare the pivot element to the element pointed by the upper bound pointer 434 | * We use the upper bound of the search scope as the reference for the comparison with the pivot element, while in the classical binary search the reference would be the desired value. 435 | * When the result of the comparison is equal (*i.e.* Case #3), we further move the upper bound, while in the classical binary search normally we would return the value immediately. 436 | 437 | ```java 438 | public int findMin(int[] nums) { 439 | int low = 0, high = nums.length - 1; 440 | while (low < high){ 441 | int mid = low + (high - low) / 2; 442 | if (nums[mid] > nums[high]){ 443 | low = mid + 1; 444 | } 445 | else if (nums[mid] < nums[high]) { 446 | high = mid; 447 | } 448 | // change the edge value since it is valid 449 | else { 450 | high--; 451 | } 452 | } 453 | return nums[low]; 454 | } 455 | ``` 456 | 457 | - Time complexity: on average $\mathcal{O}(\log_{2}{N})$ where N is the length of the array, since in general, it is a binary search algorithm. However, in the worst case where the array contains identical elements (*i.e.* case #3 `nums[pivot]==nums[high]`), the algorithm would deteriorate to iterating each element, as a result, the time complexity becomes $\mathcal{O}(N)$. 458 | - Space complexity: $\mathcal{O}(1)$, it's a constant space solution. 459 | 460 | ## Median of Two Sorted Array(Hard #4) 461 | 462 | **Question**: Given two sorted arrays `nums1` and `nums2` of size `m` and `n` respectively, return **the median** of the two sorted arrays. 463 | 464 | The overall run time complexity should be `O(log (m+n))`. 465 | 466 | **Example 1:** 467 | 468 | ``` 469 | Input: nums1 = [1,3], nums2 = [2] 470 | Output: 2.00000 471 | Explanation: merged array = [1,2,3] and median is 2. 472 | ``` 473 | 474 | **Example 2:** 475 | 476 | ``` 477 | Input: nums1 = [1,2], nums2 = [3,4] 478 | Output: 2.50000 479 | Explanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5. 480 | ``` 481 | 482 | **Constraints:** 483 | 484 | - `nums1.length == m` 485 | - `nums2.length == n` 486 | - `0 <= m <= 1000` 487 | - `0 <= n <= 1000` 488 | - `1 <= m + n <= 2000` 489 | - `-106 <= nums1[i], nums2[i] <= 106` 490 | 491 | ### My Solution 492 | 493 | ```java 494 | public double findMedianSortedArrays(int[] nums1, int[] nums2) { 495 | int length = nums1.length + nums2.length; 496 | int[] nums = new int[length]; 497 | for (int i = 0; i < nums1.length; i++){ 498 | nums[i] = nums1[i]; 499 | } 500 | for (int i = nums1.length; i < length; i++){ 501 | nums[i] = nums2[i - nums1.length]; 502 | } 503 | Arrays.sort(nums); 504 | double median = 0.0; 505 | // find the median of the aggregated array 506 | if (length % 2 == 1){ 507 | median = nums[length/ 2]; 508 | } 509 | else { 510 | median = ((double)nums[(length - 1)/ 2] + (double)nums[(length - 1) / 2 + 1]) / 2; 511 | } 512 | return median; 513 | } 514 | ``` 515 | 516 | ### Other Solution 517 | 518 | * Binary search and compare two value 519 | 520 | ```java 521 | public double findMedianSortedArrays(int[] nums1, int[] nums2) { 522 | int index1 = 0; 523 | int index2 = 0; 524 | int med1 = 0; 525 | int med2 = 0; 526 | for (int i=0; i<=(nums1.length+nums2.length)/2; i++) { 527 | med1 = med2; 528 | if (index1 == nums1.length) { 529 | med2 = nums2[index2]; 530 | index2++; 531 | } else if (index2 == nums2.length) { 532 | med2 = nums1[index1]; 533 | index1++; 534 | } else if (nums1[index1] < nums2[index2] ) { 535 | med2 = nums1[index1]; 536 | index1++; 537 | } else { 538 | med2 = nums2[index2]; 539 | index2++; 540 | } 541 | } 542 | 543 | // the median is the average of two numbers 544 | if ((nums1.length+nums2.length)%2 == 0) { 545 | return (float)(med1+med2)/2; 546 | } 547 | return med2; 548 | } 549 | ``` 550 | 551 | ## Find k-th Smallest Pair Distance (Hard #719) 552 | 553 | **Question**: The **distance of a pair** of integers `a` and `b` is defined as the absolute difference between `a` and `b`. 554 | 555 | Given an integer array `nums` and an integer `k`, return *the* `kth` *smallest **distance among all the pairs*** `nums[i]` *and* `nums[j]` *where* `0 <= i < j < nums.length`. 556 | 557 | **Example 1:** 558 | 559 | ``` 560 | Input: nums = [1,3,1], k = 1 561 | Output: 0 562 | Explanation: Here are all the pairs: 563 | (1,3) -> 2 564 | (1,1) -> 0 565 | (3,1) -> 2 566 | Then the 1st smallest distance pair is (1,1), and its distance is 0. 567 | ``` 568 | 569 | **Example 2:** 570 | 571 | ``` 572 | Input: nums = [1,1,1], k = 2 573 | Output: 0 574 | ``` 575 | 576 | **Example 3:** 577 | 578 | ``` 579 | Input: nums = [1,6,1], k = 3 580 | Output: 5 581 | ``` 582 | 583 | **Constraints:** 584 | 585 | - `n == nums.length` 586 | - `2 <= n <= 104` 587 | - `0 <= nums[i] <= 106` 588 | - `1 <= k <= n * (n - 1) / 2` 589 | 590 | ### Standard Solution 591 | 592 | #### Solution #1 Binary Search 593 | 594 | * Find low distance, and high distance, use binary search 595 | 596 | ```java 597 | public int smallestDistancePair(int[] nums, int k) { 598 | // sort the array and then do the binary search 599 | Arrays.sort(nums); 600 | 601 | int lo = 0; 602 | int hi = nums[nums.length - 1] - nums[0]; 603 | while (lo < hi) { 604 | int mi = (lo + hi) / 2; 605 | int count = 0, left = 0; 606 | for (int right = 0; right < nums.length; right++){ 607 | // find out how many count of pairs with distance <= mi 608 | while (nums[right] - nums[left] > mi) left++; 609 | count += right - left; 610 | } 611 | // count = number of pairs with distance <= mi 612 | if (count >= k) hi = mi; 613 | else lo = mi + 1; 614 | } 615 | return lo; 616 | } 617 | ``` 618 | 619 | - Time Complexity: $O(N \log{W} + N \log{N})$, where N is the length of `nums`, and W*W* is equal to `nums[nums.length - 1] - nums[0]`. The $\log W$ factor comes from our binary search, and we do $O(N)$ work inside our call to `possible` (or to calculate `count` in Java). The final $O(N\log N)$ factor comes from sorting. 620 | - Space Complexity: $O(1)$. No additional space is used except for integer variables. 621 | 622 | ## Split Array Largest Sum (Hard #410) 623 | 624 | **Question**: Given an array `nums` which consists of non-negative integers and an integer `m`, you can split the array into `m` non-empty continuous subarrays. 625 | 626 | Write an algorithm to minimize the largest sum among these `m` subarrays. 627 | 628 | **Example 1:** 629 | 630 | ``` 631 | Input: nums = [7,2,5,10,8], m = 2 632 | Output: 18 633 | Explanation: 634 | There are four ways to split nums into two subarrays. 635 | The best way is to split it into [7,2,5] and [10,8], 636 | where the largest sum among the two subarrays is only 18. 637 | ``` 638 | 639 | **Example 2:** 640 | 641 | ``` 642 | Input: nums = [1,2,3,4,5], m = 2 643 | Output: 9 644 | ``` 645 | 646 | **Example 3:** 647 | 648 | ``` 649 | Input: nums = [1,4,4], m = 3 650 | Output: 4 651 | ``` 652 | 653 | **Constraints:** 654 | 655 | - `1 <= nums.length <= 1000` 656 | - `0 <= nums[i] <= 106` 657 | - `1 <= m <= min(50, nums.length)` 658 | 659 | ### Standard Solution 660 | 661 | #### Solution #1 Binary Search 662 | 663 | * Not only use binary search on array index but also use it for value search 664 | * Check if the target value converge 665 | 666 | ```java 667 | private int minimumSubarraysRequired(int[] nums, int maxSumAllowed) { 668 | int currentSum = 0; 669 | int splitsRequired = 0; 670 | for (int element : nums) { 671 | // Add element only if the sum doesn't exceed maxSumAllowed 672 | if (currentSum + element <= maxSumAllowed) { 673 | currentSum += element; 674 | } else { 675 | // If the element addition makes sum more than maxSumAllowed 676 | // Increment the splits required and reset sum 677 | currentSum = element; 678 | splitsRequired++; 679 | } 680 | } 681 | // Return the number of subarrays, which is the number of splits + 1 682 | return splitsRequired + 1; 683 | } 684 | 685 | public int splitArray(int[] nums, int m) { 686 | // Find the sum of all elements and the maximum element 687 | int sum = 0; 688 | int maxElement = Integer.MIN_VALUE; 689 | for (int element : nums) { 690 | sum += element; 691 | maxElement = Math.max(maxElement, element); 692 | } 693 | // Define the left and right boundary of binary search 694 | int left = maxElement; 695 | int right = sum; 696 | int minimumLargestSplitSum = 0; 697 | while (left <= right) { 698 | // Find the mid value 699 | int maxSumAllowed = left + (right - left) / 2; 700 | // Find the minimum splits. If splitsRequired is less than 701 | // or equal to m move towards left i.e., smaller values 702 | if (minimumSubarraysRequired(nums, maxSumAllowed) <= m) { 703 | right = maxSumAllowed - 1; 704 | minimumLargestSplitSum = maxSumAllowed; 705 | } else { 706 | // Move towards right if splitsRequired is more than m 707 | left = maxSumAllowed + 1; 708 | } 709 | } 710 | return minimumLargestSplitSum; 711 | } 712 | ``` 713 | 714 | - Time complexity: $O(N \cdot \log(S))$, where N is the length of the array and S is the sum of integers in the array. 715 | 716 | The total number of iterations in the binary search is $\log(S)$, and for each such iteration, we call `minimumSubarraysRequired` which takes $O(N)$ time. Hence, the time complexity is equal to $O(N \cdot \log(S))$. 717 | 718 | - Space complexity: $O(1)$ 719 | 720 | We do not use any data structures that require more than constant extra space. 721 | 722 | ## 3Sum Closest (Medium #16) 723 | 724 | **Question**: Given an integer array `nums` of length `n` and an integer `target`, find three integers in `nums` such that the sum is closest to `target`. 725 | 726 | Return *the sum of the three integers*. 727 | 728 | You may assume that each input would have exactly one solution. 729 | 730 | **Example 1:** 731 | 732 | ``` 733 | Input: nums = [-1,2,1,-4], target = 1 734 | Output: 2 735 | Explanation: The sum that is closest to the target is 2. (-1 + 2 + 1 = 2). 736 | ``` 737 | 738 | **Example 2:** 739 | 740 | ``` 741 | Input: nums = [0,0,0], target = 1 742 | Output: 0 743 | ``` 744 | 745 | **Constraints:** 746 | 747 | - `3 <= nums.length <= 1000` 748 | - `-1000 <= nums[i] <= 1000` 749 | - `-104 <= target <= 104` 750 | 751 | ### Standard Solution 752 | 753 | #### Solution #1 Two Pointers 754 | 755 | * Use for loop and three-pointers, calculate the sum 756 | * Compare the sum with the target, if smaller, move the low pointer, if higher, move the high pointer 757 | 758 | ```java 759 | public int threeSumClosest(int[] nums, int target) { 760 | int diff = Integer.MAX_VALUE; 761 | int sz = nums.length; 762 | Arrays.sort(nums); 763 | for (int i = 0; i < sz && diff != 0; i++){ 764 | int lo = i + 1; 765 | int hi = sz - 1; 766 | while (lo < hi){ 767 | int sum = nums[i] + nums[lo] + nums[hi]; 768 | if (Math.abs(target - sum) < Math.abs(diff)){ 769 | diff = target - sum; 770 | } 771 | if (sum < target){ 772 | lo++; 773 | } 774 | else { 775 | hi--; 776 | } 777 | } 778 | } 779 | return target - diff; 780 | } 781 | ``` 782 | 783 | - Time Complexity: $\mathcal{O}(n^2)$. We have outer and inner loops, each going through n elements. 784 | 785 | Sorting the array takes $\mathcal{O}(n\log{n})$, so overall complexity is $\mathcal{O}(n\log{n} + n^2)$. This is asymptotically equivalent to $\mathcal{O}(n^2)$. 786 | 787 | - Space Complexity: from $\mathcal{O}(\log{n})$ to $\mathcal{O}(n)$, depending on the implementation of the sorting algorithm. 788 | 789 | #### Solution #2 Binary Search 790 | 791 | ```java 792 | public int threeSumClosest(int[] nums, int target) { 793 | int diff = Integer.MAX_VALUE; 794 | int sz = nums.length; 795 | Arrays.sort(nums); 796 | for (int i = 0; i < sz && diff != 0; ++i) { 797 | for (int j = i + 1; j < sz - 1; ++j) { 798 | int complement = target - nums[i] - nums[j]; 799 | var idx = Arrays.binarySearch(nums, j + 1, sz - 1, complement); 800 | int hi = idx >= 0 ? idx : ~idx, lo = hi - 1; 801 | if (hi < sz && Math.abs(complement - nums[hi]) < Math.abs(diff)) 802 | diff = complement - nums[hi]; 803 | if (lo > j && Math.abs(complement - nums[lo]) < Math.abs(diff)) 804 | diff = complement - nums[lo]; 805 | } 806 | } 807 | return target - diff; 808 | } 809 | ``` 810 | 811 | - Time Complexity: $\mathcal{O}(n^2\log{n})$. Binary search takes $\mathcal{O}(\log{n})$, and we do it n times in the inner loop. Since we are going through n elements in the outer loop, the overall complexity is $\mathcal{O}(n^2\log{n})$. 812 | - Space Complexity: from $\mathcal{O}(\log{n})$ to $\mathcal{O}(n)$, depending on the implementation of the sorting algorithm. -------------------------------------------------------------------------------- /Binary Tree/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Binary Tree/.DS_Store -------------------------------------------------------------------------------- /Binary Tree/Binary Tree Tutorial.md: -------------------------------------------------------------------------------- 1 | # Binary Tree Tutorial 2 | 3 | Materials refer to: https://leetcode.com/explore/learn/card/recursion-i/ 4 | 5 | # Introduction to Binary Tree 6 | 7 | * From graph view, a tree can also be defined as a directed acyclic graph which has `N nodes` and `N-1 edges`. 8 | * As the name suggests, a binary tree is a tree data structure in which each node has `at most two children`, which are referred to as the left child and the right child. 9 | 10 | ## Traverse a Tree 11 | 12 | * **Pre-order Traversal** 13 | 14 | * Pre-order traversal is to visit the root first. Then traverse the left subtree. Finally, traverse the right subtree. 15 | 16 | * **In-order Traversal** 17 | 18 | * In-order traversal is to traverse the left subtree first. Then visit the root. Finally, traverse the right subtree. 19 | * Typically, for `binary search tree`, we can retrieve all the data in sorted order using in-order traversal. 20 | 21 | * **Post-order Traversal** 22 | 23 | * Post-order traversal is to traverse the left subtree first. Then traverse the right subtree. Finally, visit the root. 24 | * It is worth noting that when you delete nodes in a tree, **deletion process will be in post-order**. That is to say, when you delete a node, you will delete its left child and its right child before you delete the node itself. 25 | * Post-order is widely use in mathematical expression. It is easier to write a program to parse a post-order expression. 26 | 27 | img 28 | 29 | * If you handle this tree in postorder, you can easily handle the expression using a stack. 30 | * Each time when you meet a operator, you can just **pop 2 elements from the stack**, **calculate the result and push the result back into the stack**. 31 | 32 | * Frequently come with **recursion and/or iteration** for code solution. 33 | 34 | * To be cautious, the complexity might be different due to a different implementation. It is comparatively easy to do traversal recursively but when the depth of the tree is too large, we might suffer from `stack overflow` problem. That's one of the main reasons why we want to solve this problem iteratively sometimes. 35 | 36 | ## Binary Tree Preorder Traversal(Easy #144) 37 | 38 | **Question**: Given the `root` of a binary tree, return *the preorder traversal of its nodes' values*. 39 | 40 | **Example 1:** 41 | 42 | img 43 | 44 | ``` 45 | Input: root = [1,null,2,3] 46 | Output: [1,2,3] 47 | ``` 48 | 49 | **Example 2:** 50 | 51 | ``` 52 | Input: root = [] 53 | Output: [] 54 | ``` 55 | 56 | **Example 3:** 57 | 58 | ``` 59 | Input: root = [1] 60 | Output: [1] 61 | ``` 62 | 63 | **Constraints:** 64 | 65 | - The number of nodes in the tree is in the range `[0, 100]`. 66 | - `-100 <= Node.val <= 100` 67 | 68 | ### My Solution 69 | 70 | ```java 71 | /** 72 | * Definition for a binary tree node. 73 | * public class TreeNode { 74 | * int val; 75 | * TreeNode left; 76 | * TreeNode right; 77 | * TreeNode() {} 78 | * TreeNode(int val) { this.val = val; } 79 | * TreeNode(int val, TreeNode left, TreeNode right) { 80 | * this.val = val; 81 | * this.left = left; 82 | * this.right = right; 83 | * } 84 | * } 85 | */ 86 | public List preorderTraversal(TreeNode root) { 87 | List nodes = new ArrayList<>(); 88 | preorder(root, nodes); 89 | return nodes; 90 | } 91 | 92 | public void preorder(TreeNode root, List nodes){ 93 | if (root == null){ 94 | return; 95 | } else { 96 | nodes.add(root.val); 97 | preorder(root.left, nodes); 98 | preorder(root.right, nodes); 99 | } 100 | } 101 | ``` 102 | 103 | * Recursion method: 104 | * Add the root first 105 | * Traverse the left tree 106 | * Traverse the right tree 107 | * The traversal method **return void**, separate it from the main method 108 | * While traverse, bring in the tree with the nodes record 109 | * Always use `if-else`, `if` for the base case and `return` 110 | 111 | ### Standard Solution 112 | 113 | * There are two general strategies to traverse a tree: 114 | 115 | - ***Breadth First Search* (`BFS`)** 116 | 117 | We scan through the tree level by level, following the order of height, from top to bottom. The nodes on higher level would be visited before the ones with lower levels. 118 | 119 | - ***Depth First Search* (`DFS`)** 120 | 121 | In this strategy, we adopt the `depth` as the priority, so that one would start from a root and reach all the way down to certain leaf, and then back to root to reach another branch. 122 | 123 | The DFS strategy can further be distinguished as `preorder`, `inorder`, and `postorder` depending on the relative order among the root node, left node and right node. 124 | 125 | ![postorder](https://leetcode.com/problems/binary-tree-preorder-traversal/Figures/145_transverse.png) 126 | 127 | #### Solution #1 Recursion 128 | 129 | * Same as my solution 130 | * Time complexity: $O(N)$. N is the number of nodes, each node is traversed once. 131 | * Space complexity: $O(N)$. Average case should be $O(\log n)$, the worst case is a linked-list-like tree then it would be a $O(N)$ 132 | 133 | #### Solution #2 Iteration 134 | 135 | * Use a stack to hold the values, each time we encouter a null, we pop one element from the stack 136 | 137 | ```java 138 | public List preorderTraversal(TreeNode root){ 139 | Stack stack = new Stack<>(); 140 | LinkedList output = new LinkedList<>(); 141 | if (root == null){ 142 | return output; 143 | } 144 | 145 | stack.add(root); 146 | while(!stack.isEmpty()){ 147 | TreeNode node = stack.pop(); 148 | output.add(node.val); 149 | if (node.right != null){// since it is a stack, we need to exchange the order here with left 150 | stack.add(node.right); 151 | } 152 | if (node.left != null){ 153 | stack.add(node.left); 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | * Time complexity: $O(N)$. N is the number of nodes, each node is traversed once. 160 | * Space complexity: $O(N)$. Average case should be $O(\log n)$, the worst case is a linked-list-like tree then it would be a $O(N)$ 161 | 162 | #### Solution #3 Morris Traversal(not important) 163 | 164 | * The algorithm does not use additional space for the computation, and the memory is only used to keep the output. If one prints the output directly along the computation, the space complexity would be $ \mathcal{O}(1)$. 165 | * Details refer to [morris traversal leetcode](https://leetcode.com/problems/binary-tree-preorder-traversal/solution/) 166 | 167 | ```java 168 | public List preorderTraversal(TreeNode root){ 169 | LinkedList output = new LinkedList<>(); 170 | 171 | TreeNode node = root; 172 | while(node != null){ 173 | if(node.left == null){ 174 | output.add(node.val); 175 | node = node.right; 176 | } 177 | else { 178 | TreeNode predecessor = node.left; 179 | while((predecessor.right != null) && (predecessor.right != node)){ 180 | predecessor = predecessor.right; 181 | } 182 | if (predecessor.right == null){ 183 | output.add(node.val); 184 | predecessor.right = node; 185 | node = node.left; 186 | } 187 | else { 188 | predecessor.right = null; 189 | node = node.right; 190 | } 191 | } 192 | } 193 | return output; 194 | } 195 | ``` 196 | 197 | - Time complexity : we visit each predecessor exactly twice descending down from the node, thus the time complexity is $\mathcal{O}(N)$, where N is the number of nodes, *i.e.* the size of tree. 198 | - Space complexity : we use no additional memory for the computation itself, thus space complexity is $\mathcal{O}(1)$. 199 | 200 | ## Binary Tree Inorder Traversal(Easy #94) 201 | 202 | **Question**: Given the `root` of a binary tree, return *the inorder traversal of its nodes' values*. 203 | 204 | **Example 1:** 205 | 206 | img 207 | 208 | ``` 209 | Input: root = [1,null,2,3] 210 | Output: [1,3,2] 211 | ``` 212 | 213 | **Example 2:** 214 | 215 | ``` 216 | Input: root = [] 217 | Output: [] 218 | ``` 219 | 220 | **Example 3:** 221 | 222 | ``` 223 | Input: root = [1] 224 | Output: [1] 225 | ``` 226 | 227 | **Constraints:** 228 | 229 | - The number of nodes in the tree is in the range `[0, 100]`. 230 | - `-100 <= Node.val <= 100` 231 | 232 | ### My Solution 233 | 234 | ```java 235 | public List inorderTraversal(TreeNode root){ 236 | List nodes = new ArrayList<>(); 237 | inorder(root, nodes); 238 | return nodes; 239 | } 240 | 241 | public void inorder(TreeNode root, List nodes){ 242 | if (root == null) return; 243 | else { 244 | inorder(root.left, nodes); 245 | nodes.add(root.val); 246 | inorder(root.right, nodes); 247 | } 248 | } 249 | ``` 250 | 251 | ### Standard Solution 252 | 253 | #### Solution #1 Recursion 254 | 255 | * Same as my solution 256 | * Time complexity: $O(N)$ because the recursive function is $T(n) = 2 \cdot T(n/2)+1$. 257 | * Space complexity: $O(N)$. The worst case space is $O(N)$, and in average case it is $O(\log n)$ 258 | 259 | #### Solution #2 Interation using Stack 260 | 261 | ```java 262 | public List inorderTraversal(TreeNode root){ 263 | List res = new ArrayList<>(); 264 | Stack stack = new Stack<>(); 265 | TreeNode curr = root; 266 | while (curr != null || !stack.isEmpty()){ 267 | while (curr != null){ 268 | stack.push(curr); 269 | curr = curr.left; 270 | } 271 | curr = stack.pop(); 272 | res.add(curr.val); 273 | curr = curr.right; 274 | } 275 | } 276 | ``` 277 | 278 | ```java 279 | class Solution { 280 | public List inorderTraversal(TreeNode root) { 281 | List result = new ArrayList<>(); 282 | if (root == null){ 283 | return result; 284 | } 285 | Stack stack = new Stack<>(); 286 | TreeNode cur =root; 287 | while (cur!=null || !stack.isEmpty()) { 288 | if (cur!=null) { 289 | stack.push(cur); 290 | cur = cur.left; 291 | }else { 292 | cur = stack.pop(); 293 | result.add(cur.val); 294 | cur = cur.right; 295 | } 296 | } 297 | return result; 298 | } 299 | } 300 | 301 | 作者:mulberry_qs 302 | 链接:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/solution/duo-chong-bian-li-tou-da-by-mulberry_qs-02c9/ 303 | 来源:力扣(LeetCode) 304 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 305 | ``` 306 | 307 | * Time complexity: $O(N)$ because the recursive function is $T(n) = 2 \cdot T(n/2)+1$. 308 | * Space complexity: $O(N)$. The worst case space is $O(N)$, and in average case it is $O(\log n)$ 309 | 310 | #### Solution #3 Morris Traversal 311 | 312 | ```java 313 | class Solution { 314 | public List inorderTraversal(TreeNode root) { 315 | List res = new ArrayList<>(); 316 | TreeNode curr = root; 317 | TreeNode pre; 318 | while (curr != null) { 319 | if (curr.left == null) { 320 | res.add(curr.val); 321 | curr = curr.right; // move to next right node 322 | } else { // has a left subtree 323 | pre = curr.left; 324 | while (pre.right != null) { // find rightmost 325 | pre = pre.right; 326 | } 327 | pre.right = curr; // put cur after the pre node 328 | TreeNode temp = curr; // store cur node 329 | curr = curr.left; // move cur to the top of the new tree 330 | temp.left = null; // original cur left be null, avoid infinite loops 331 | } 332 | } 333 | return res; 334 | } 335 | } 336 | ``` 337 | 338 | * Time complexity: $O(N)$ 339 | * Space complexity: $O(1)$ 340 | 341 | ## Binary Tree Postorder Traversal(Easy #145) 342 | 343 | **Question**: Given the `root` of a binary tree, return *the postorder traversal of its nodes' values*. 344 | 345 | **Example 1:** 346 | 347 | ![img](https://assets.leetcode.com/uploads/2020/08/28/pre1.jpg) 348 | 349 | ``` 350 | Input: root = [1,null,2,3] 351 | Output: [3,2,1] 352 | ``` 353 | 354 | **Example 2:** 355 | 356 | ``` 357 | Input: root = [] 358 | Output: [] 359 | ``` 360 | 361 | **Example 3:** 362 | 363 | ``` 364 | Input: root = [1] 365 | Output: [1] 366 | ``` 367 | 368 | **Constraints:** 369 | 370 | - The number of the nodes in the tree is in the range `[0, 100]`. 371 | - `-100 <= Node.val <= 100` 372 | 373 | ### My Solution 374 | 375 | ```java 376 | public List postorderTraversal(TreeNode root){ 377 | List nodes = new ArrayList<>(); 378 | postorder(root, nodes); 379 | return nodes; 380 | } 381 | 382 | public void postorder(TreeNode root, List nodes){ 383 | if (root == null){ 384 | return; 385 | } 386 | else { 387 | postorder(root.left, nodes); 388 | postorder(root.right, nodes); 389 | nodes.add(root.val); 390 | } 391 | } 392 | ``` 393 | 394 | ```java 395 | class Solution { 396 | public List postorderTraversal(TreeNode root) { 397 | List result = new ArrayList<>(); 398 | if (root == null) { 399 | return result; 400 | } 401 | Stack stack = new Stack<>(); // 入栈为中左右 出栈为中右左 402 | stack.push(root); 403 | while (!stack.isEmpty()) { 404 | TreeNode treeNode = stack.pop(); 405 | result.add(treeNode.val); 406 | if (treeNode.left != null) { 407 | stack.push(treeNode.left); 408 | } 409 | if (treeNode.right != null) { 410 | stack.push(treeNode.right); 411 | } 412 | } 413 | Collections.reverse(result); // 直接翻转中右左为左右中 414 | return result; 415 | } 416 | } 417 | 418 | 作者:mulberry_qs 419 | 链接:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/solution/duo-chong-bian-li-tou-da-by-mulberry_qs-02c9/ 420 | 来源:力扣(LeetCode) 421 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 422 | ``` 423 | 424 | * Time complexity and space complexity should be the same as previous problems 425 | * No given standard solution in leetcode but should be similar to previous problems 426 | 427 | # Level-order Traversal - Introduction 428 | 429 | * Level-order traversal is to traverse the tree level by level. 430 | * `Breadth-First Search` is an algorithm to traverse or search in data structures like a tree or a graph. 431 | * Typically, we use a **queue** to help us to do BFS. 432 | 433 | ## Binary Tree Level Order Traversal(Medium 102) 434 | 435 | **Question**: Given the `root` of a binary tree, return *the level order traversal of its nodes' values*. (i.e., from left to right, level by level). 436 | 437 | **Example 1:** 438 | 439 | img 440 | 441 | ``` 442 | Input: root = [3,9,20,null,null,15,7] 443 | Output: [[3],[9,20],[15,7]] 444 | ``` 445 | 446 | **Example 2:** 447 | 448 | ``` 449 | Input: root = [1] 450 | Output: [[1]] 451 | ``` 452 | 453 | **Example 3:** 454 | 455 | ``` 456 | Input: root = [] 457 | Output: [] 458 | ``` 459 | 460 | **Constraints:** 461 | 462 | - The number of nodes in the tree is in the range `[0, 2000]`. 463 | - `-1000 <= Node.val <= 1000` 464 | 465 | ### My Solution 466 | 467 | * Use a queue to store the value from left to right 468 | * Use queue size to determine how many nodes in the same level and use array list to store the same level nodes 469 | * Add the level nodes to `levels` which is the return list 470 | 471 | ```java 472 | /** 473 | * Definition for a binary tree node. 474 | * public class TreeNode { 475 | * int val; 476 | * TreeNode left; 477 | * TreeNode right; 478 | * TreeNode() {} 479 | * TreeNode(int val) { this.val = val; } 480 | * TreeNode(int val, TreeNode left, TreeNode right) { 481 | * this.val = val; 482 | * this.left = left; 483 | * this.right = right; 484 | * } 485 | * } 486 | */ 487 | public List> levelOrder(TreeNode root) { 488 | Queue treeQueue = new LinkedList<>(); 489 | List> levels = new ArrayList<>(); 490 | if (root == null) return levels; 491 | treeQueue.add(root); 492 | while(!treeQueue.isEmpty()){ 493 | List list = new ArrayList<>(); 494 | int level_length = treeQueue.size(); 495 | for (int i = 0; i < level_length; i++){ 496 | TreeNode node = treeQueue.remove(); 497 | list.add(node.val); 498 | if (node.left != null){ 499 | treeQueue.add(node.left); 500 | } 501 | if (node.right != null){ 502 | treeQueue.add(node.right); 503 | } 504 | } 505 | levels.add(list); 506 | } 507 | return levels; 508 | } 509 | ``` 510 | 511 | ```java 512 | // second attempt 513 | public List> levelOrder(TreeNode root) { 514 | List> res = new ArrayList<>(); 515 | bfs(root, res, 0); 516 | return res; 517 | } 518 | 519 | public void bfs(TreeNode root, List> res, int level){ 520 | if (root == null){ 521 | return; 522 | } 523 | if (res.size() == level){ 524 | res.add(new ArrayList()); 525 | } 526 | res.get(level).add(root.val); 527 | bfs(root.left, res, level + 1); 528 | bfs(root.right, res, level + 1); 529 | } 530 | ``` 531 | 532 | ```java 533 | // third attempt 534 | public List> levelOrder(TreeNode root) { 535 | // level order traversal: BFS 536 | // 1. create the res list, create a queue 537 | List> res = new ArrayList<>(); 538 | Queue queue = new LinkedList<>(); 539 | if (root == null) return res; 540 | queue.add(root); 541 | int level = 0; 542 | // 2. use queue to store the root node, left, right 543 | while(!queue.isEmpty()){ 544 | res.add(new ArrayList()); 545 | int levelLength = queue.size(); 546 | 547 | for (int i = 0; i < levelLength; i++){ 548 | // 3. poll from queue, put element to the list 549 | TreeNode curr = queue.poll(); 550 | res.get(level).add(curr.val); 551 | if (curr.left != null) queue.add(curr.left); 552 | if (curr.right != null) queue.add(curr.right); 553 | } 554 | level++; 555 | } 556 | return res; 557 | } 558 | ``` 559 | 560 | 561 | 562 | ### Standard Solution 563 | 564 | #### Solution #1 Iteration 565 | 566 | * Same as my solution 567 | * Time complexity : $\mathcal{O}(N)$ since each node is processed exactly once. 568 | * Space complexity : $\mathcal{O}(N)$ to keep the output structure which contains `N` node values. 569 | 570 | #### Solution #2 Recursion 571 | 572 | * Call recursive helper function 573 | * Same concept with the method 1 but using recursion 574 | 575 | ```java 576 | class Solution { 577 | List> levels = new ArrayList>(); 578 | public void helper(TreeNode node, int level) { 579 | // start the current level 580 | if (levels.size() == level) 581 | levels.add(new ArrayList()); 582 | 583 | // fulfil the current level 584 | levels.get(level).add(node.val); 585 | 586 | // process child nodes for the next level 587 | if (node.left != null) 588 | helper(node.left, level + 1); 589 | if (node.right != null) 590 | helper(node.right, level + 1); 591 | } 592 | public List> levelOrder(TreeNode root) { 593 | if (root == null) return levels; 594 | helper(root, 0); 595 | return levels; 596 | } 597 | } 598 | ``` 599 | 600 | * Time complexity : $\mathcal{O}(N)$ since each node is processed exactly once. 601 | * Space complexity : $\mathcal{O}(N)$ to keep the output structure which contains `N` node values. 602 | 603 | # Solve Tree Problem Recursively 604 | 605 | As we know, a tree can be defined recursively as a node(the root node) that includes a value and a list of references to children nodes. Recursion is one of the natural features of a tree. Therefore, many tree problems can be solved recursively. For each recursive function call, we only focus on the problem for the current node and call the function recursively to solve its children. 606 | 607 | Typically, we can solve a tree problem recursively using a top-down approach or using a bottom-up approach. 608 | 609 | ## Top-down Solution 610 | 611 | * "Top-down" means that in each recursive call, we will visit the node first to come up with some values, and pass these values to its children when calling the function recursively. 612 | 613 | * So the "top-down" solution can be considered as a kind of **preorder** traversal. 614 | 615 | * To be specific, the recursive function `top_down(root, params)` works like this: 616 | 617 | ``` 618 | 1. return specific value for null node 619 | 2. update the answer if needed // answer <-- params 620 | 3. left_ans = top_down(root.left, left_params) // left_params <-- root.val, params 621 | 4. right_ans = top_down(root.right, right_params) // right_params <-- root.val, params 622 | 5. return the answer if needed // answer <-- left_ans, right_ans 623 | ``` 624 | 625 | * Find maximum depth of a binary tree: 626 | 627 | ``` 628 | 1. return if root is null 629 | 2. if root is a leaf node: 630 | 3. answer = max(answer, depth) // update the answer if needed 631 | 4. maximum_depth(root.left, depth + 1) // call the function recursively for left child 632 | 5. maximum_depth(root.right, depth + 1) // call the function recursively for right child 633 | ``` 634 | 635 | ```java 636 | private int answer; // don't forget to initialize answer before call maximum_depth 637 | private void maximum_depth(TreeNode root, int depth) { 638 | if (root == null) { 639 | return; 640 | } 641 | if (root.left == null && root.right == null) { 642 | answer = Math.max(answer, depth); 643 | } 644 | maximum_depth(root.left, depth + 1); 645 | maximum_depth(root.right, depth + 1); 646 | } 647 | ``` 648 | 649 | ## Bottom-up Solution 650 | 651 | * In each recursive call, we will firstly call the function recursively for all the children nodes and then come up with the answer according to the returned values and the value of the current node itself. 652 | 653 | * This process can be regarded as a kind of **postorder** traversal. 654 | 655 | * Typically, a "bottom-up" recursive function `bottom_up(root)` will be something like this: 656 | 657 | ``` 658 | 1. return specific value for null node 659 | 2. left_ans = bottom_up(root.left) // call function recursively for left child 660 | 3. right_ans = bottom_up(root.right) // call function recursively for right child 661 | 4. return answers // answer <-- left_ans, right_ans, root.val 662 | ``` 663 | 664 | * Maximum depth: 665 | 666 | ``` 667 | 1. return 0 if root is null // return 0 for null node 668 | 2. left_depth = maximum_depth(root.left) 669 | 3. right_depth = maximum_depth(root.right) 670 | 4. return max(left_depth, right_depth) + 1 // return depth of the subtree rooted at root 671 | ``` 672 | 673 | ```java 674 | public int maximum_depth(TreeNode root) { 675 | if (root == null) { 676 | return 0; // return 0 for null node 677 | } 678 | int left_depth = maximum_depth(root.left); 679 | int right_depth = maximum_depth(root.right); 680 | return Math.max(left_depth, right_depth) + 1; // return depth of the subtree rooted at root 681 | } 682 | ``` 683 | 684 | -------------------------------------------------------------------------------- /Bit Manipulation/Bit Problems Part #1.md: -------------------------------------------------------------------------------- 1 | # Bit Problems Part #1 2 | 3 | ## Sort Integers by The Number of 1 Bit (Easy #1356) 4 | 5 | **Question**: You are given an integer array `arr`. Sort the integers in the array in ascending order by the number of `1`'s in their binary representation and in case of two or more integers have the same number of `1`'s you have to sort them in ascending order. 6 | 7 | Return *the array after sorting it*. 8 | 9 | **Example 1:** 10 | 11 | ``` 12 | Input: arr = [0,1,2,3,4,5,6,7,8] 13 | Output: [0,1,2,4,8,3,5,6,7] 14 | Explantion: [0] is the only integer with 0 bits. 15 | [1,2,4,8] all have 1 bit. 16 | [3,5,6] have 2 bits. 17 | [7] has 3 bits. 18 | The sorted array by bits is [0,1,2,4,8,3,5,6,7] 19 | ``` 20 | 21 | **Example 2:** 22 | 23 | ``` 24 | Input: arr = [1024,512,256,128,64,32,16,8,4,2,1] 25 | Output: [1,2,4,8,16,32,64,128,256,512,1024] 26 | Explantion: All integers have 1 bit in the binary representation, you should just sort them in ascending order. 27 | ``` 28 | 29 | **Constraints:** 30 | 31 | - `1 <= arr.length <= 500` 32 | - `0 <= arr[i] <= 104` 33 | 34 | ### Standard Solution 35 | 36 | #### Solution #1 Bit Count 37 | 38 | * The **bitCount()** method of Integer class of java.lang package returns the count of the number of one-bits in the two’s complement binary representation of an int value. This function is sometimes referred to as the **population count**. 39 | 40 | ```java 41 | public int[] sortByBits(int[] arr) { 42 | Integer[] boxedArray = Arrays.stream(arr).boxed().toArray(Integer[]::new); 43 | 44 | Arrays.sort(boxedArray, (a,b) -> { 45 | int A = Integer.bitCount(a); 46 | int B = Integer.bitCount(b); 47 | if(A < B) return -1; 48 | if(A > B) return 1; 49 | return Integer.compare(a,b); 50 | }); 51 | 52 | return Arrays.stream(boxedArray).mapToInt(Integer::intValue).toArray(); 53 | } 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /Bit/Bit Manipulation Part #1.md: -------------------------------------------------------------------------------- 1 | # Bit Manipulation Part #1 2 | 3 | ## Maximum XOR After Operations (Medium #2317) 4 | 5 | **Question**: You are given a **0-indexed** integer array `nums`. In one operation, select **any** non-negative integer `x` and an index `i`, then **update** `nums[i]` to be equal to `nums[i] AND (nums[i] XOR x)`. 6 | 7 | Note that `AND` is the bitwise AND operation and `XOR` is the bitwise XOR operation. 8 | 9 | Return *the **maximum** possible bitwise XOR of all elements of* `nums` *after applying the operation **any number** of times*. 10 | 11 | **Example 1:** 12 | 13 | ``` 14 | Input: nums = [3,2,4,6] 15 | Output: 7 16 | Explanation: Apply the operation with x = 4 and i = 3, num[3] = 6 AND (6 XOR 4) = 6 AND 2 = 2. 17 | Now, nums = [3, 2, 4, 2] and the bitwise XOR of all the elements = 3 XOR 2 XOR 4 XOR 2 = 7. 18 | It can be shown that 7 is the maximum possible bitwise XOR. 19 | Note that other operations may be used to achieve a bitwise XOR of 7. 20 | ``` 21 | 22 | **Example 2:** 23 | 24 | ``` 25 | Input: nums = [1,2,3,9,2] 26 | Output: 11 27 | Explanation: Apply the operation zero times. 28 | The bitwise XOR of all the elements = 1 XOR 2 XOR 3 XOR 9 XOR 2 = 11. 29 | It can be shown that 11 is the maximum possible bitwise XOR. 30 | ``` 31 | 32 | **Constraints:** 33 | 34 | - `1 <= nums.length <= 105` 35 | - `0 <= nums[i] <= 108` 36 | 37 | ### Standard Solution 38 | 39 | * Now we approve it's realizable. Assume result is `best = XOR(A[i])` and `best < res` above. There is at least one bit difference between `best` and `res`, assume it's `x = 1 << k`. We can find at least a `A[i]` that `A[i] && x = x`. 40 | * We apply `x` on `A[i]`, `A[i]` is updated to `A[i] && (A[i] ^ x) = A[i] ^ x`. We had `best = XOR(A[i])` as said above, 41 | now we have `best2 = XOR(A[i]) ^ x`, so we get a better `best2 > best`, where we prove by contradiction. 42 | 43 | ```java 44 | public int maximumXOR(int[] nums) { 45 | int res = 0; 46 | for (int a: nums) 47 | res |= a; 48 | return res; 49 | } 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /Common-method-cheet-sheet.md: -------------------------------------------------------------------------------- 1 | # Commonly Used Method in LeetCode 2 | Authored by: hhhwenjun 3 | 4 | ## Integer, String, Character Transformation 5 | #### Char to Int 6 | 1. Subtract with '0' to find the value: `char - '0'` 7 | 2. Use built-in character method to get the numeric value: `Character.getNumericValue(char)` 8 | **Tips**: Do not use `Integer.valueOf` or `Integer.parseInt`, they would only show the char value, such as 'a' -> 67 9 | -------------------------------------------------------------------------------- /Dynamic Programming/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Dynamic Programming/.DS_Store -------------------------------------------------------------------------------- /Graph/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Graph/.DS_Store -------------------------------------------------------------------------------- /Graph/Graph Part #1.md: -------------------------------------------------------------------------------- 1 | # Graph Part #1 2 | 3 | ## Maximum Total Importance of Roads (Medium #2285) 4 | 5 | **Question**: You are given an int `n` denoting the number of cities in a country. The cities are numbered from `0` to `n - 1`. 6 | 7 | You are also given a 2D integer array `roads` where `roads[i] = [ai, bi]` denotes that there exists a **bidirectional** road connecting cities `ai` and `bi`. 8 | 9 | You need to assign each city with an integer value from `1` to `n`, where each value can only be used **once**. The **importance** of a road is then defined as the **sum** of the values of the two cities it connects. 10 | 11 | Return *the **maximum total importance** of all roads possible after assigning the values optimally.* 12 | 13 | **Example 1:** 14 | 15 | img 16 | 17 | ``` 18 | Input: n = 5, roads = [[0,1],[1,2],[2,3],[0,2],[1,3],[2,4]] 19 | Output: 43 20 | Explanation: The figure above shows the country and the assigned values of [2,4,5,3,1]. 21 | - The road (0,1) has an importance of 2 + 4 = 6. 22 | - The road (1,2) has an importance of 4 + 5 = 9. 23 | - The road (2,3) has an importance of 5 + 3 = 8. 24 | - The road (0,2) has an importance of 2 + 5 = 7. 25 | - The road (1,3) has an importance of 4 + 3 = 7. 26 | - The road (2,4) has an importance of 5 + 1 = 6. 27 | The total importance of all roads is 6 + 9 + 8 + 7 + 7 + 6 = 43. 28 | It can be shown that we cannot obtain a greater total importance than 43. 29 | ``` 30 | 31 | **Example 2:** 32 | 33 | img 34 | 35 | ``` 36 | Input: n = 5, roads = [[0,3],[2,4],[1,3]] 37 | Output: 20 38 | Explanation: The figure above shows the country and the assigned values of [4,3,2,5,1]. 39 | - The road (0,3) has an importance of 4 + 5 = 9. 40 | - The road (2,4) has an importance of 2 + 1 = 3. 41 | - The road (1,3) has an importance of 3 + 5 = 8. 42 | The total importance of all roads is 9 + 3 + 8 = 20. 43 | It can be shown that we cannot obtain a greater total importance than 20. 44 | ``` 45 | 46 | **Constraints:** 47 | 48 | - `2 <= n <= 5 * 104` 49 | - `1 <= roads.length <= 5 * 104` 50 | - `roads[i].length == 2` 51 | - `0 <= ai, bi <= n - 1` 52 | - `ai != bi` 53 | - There are no duplicate roads. 54 | 55 | ### Standard Solution 56 | 57 | #### Solution #1 Count Degree 58 | 59 | - The number assigned to any node will contribute to its neighbors, which is equal to its degree. 60 | - So, give the maximum number to the node with the maximum degree (edges connected to it). 61 | 62 | ```java 63 | public long maximumImportance(int n, int[][] roads) { 64 | long ans = 0, x = 1; 65 | long degree[] = new long[n]; 66 | for(int road[] : roads){ 67 | degree[road[0]]++; // count degree for node 68 | degree[road[1]]++; 69 | } 70 | Arrays.sort(degree); 71 | for(long i : degree) ans += i * (x++) ; // assign number to the degree 72 | return ans; 73 | } 74 | ``` 75 | 76 | * The time complexity is $O(n \log n)$ due to sorting 77 | * The space complexity is $O(n)$ for the length of n -------------------------------------------------------------------------------- /Greedy/Greedy Algorithm Part #1.md: -------------------------------------------------------------------------------- 1 | # Greedy Algorithm Part #1 2 | 3 | ## Remove Covered Intervals(Medium #1288) 4 | 5 | **Question**: Given an array `intervals` where `intervals[i] = [li, ri]` represent the interval `[li, ri)`, remove all intervals that are covered by another interval in the list. 6 | 7 | The interval `[a, b)` is covered by the interval `[c, d)` if and only if `c <= a` and `b <= d`. 8 | 9 | Return *the number of remaining intervals*. 10 | 11 | **Example 1:** 12 | 13 | ``` 14 | Input: intervals = [[1,4],[3,6],[2,8]] 15 | Output: 2 16 | Explanation: Interval [3,6] is covered by [2,8], therefore it is removed. 17 | ``` 18 | 19 | **Example 2:** 20 | 21 | ``` 22 | Input: intervals = [[1,4],[2,3]] 23 | Output: 1 24 | ``` 25 | 26 | **Constraints:** 27 | 28 | - `1 <= intervals.length <= 1000` 29 | - `intervals[i].length == 2` 30 | - `0 <= li <= ri <= 105` 31 | - All the given intervals are **unique**. 32 | 33 | ### Standard Solution 34 | 35 | #### Solution #1 Greedy Algorithm 36 | 37 | * The typical greedy solution has $\mathcal{O}(N \log N)$ time complexity and consists of two steps: 38 | 39 | - Figure out how to sort the input data. That would take $\mathcal{O}(N \log N)$ time, and could be done directly by sorting or indirectly by using the heap data structure. Usually sorting is better than heap usage because of gain in space. 40 | - Parse the sorted input in $\mathcal{O}(N)$ time to construct a solution. 41 | 42 | traversal 43 | 44 | * Let us consider two subsequent intervals after sorting. Since sorting ensures that `start1 < start2`, it's sufficient to compare the end boundaries: 45 | - If `end1 < end2`, the intervals won't completely cover one another, though they have some overlapping. 46 | - If `end1 >= end2`, interval 2 is covered by interval 1. 47 | * Edge case: **How to treat intervals that share start point** 48 | * If two intervals share the same start point, one has to put the longer interval in front. 49 | 50 | traversal 51 | 52 | ```java 53 | public int removeCoveredIntervals(int[][] intervals) { 54 | Arrays.sort(intervals, new Comparator(){ 55 | @Override 56 | public int compare(int[] o1, int[] o2){ 57 | // sort by start point 58 | // if two intervals share same start point, 59 | // put the longer one to be first 60 | return o1[0] == o2[0] ? o2[1] - o1[1] : o1[0] - o2[0]; 61 | } 62 | }); 63 | int count = 0; 64 | int end, prev_end = 0; 65 | for (int[] curr : intervals){ 66 | end = curr[1]; 67 | // if current interval is not covered 68 | // by the previous one 69 | if (prev_end < end){ 70 | ++count; 71 | prev_end = end; 72 | } 73 | } 74 | return count; 75 | } 76 | ``` 77 | 78 | * Time complexity: $\mathcal{O}(N \log N)$ since the sorting dominates the complexity of the algorithm. 79 | * Space complexity: $\mathcal{O}(N)$ or $\mathcal{O}(\log{N})$ 80 | - The space complexity of the sorting algorithm depends on the implementation of each programming language. 81 | - For instance, the `sorted()` function in Python is implemented with the [Timsort](https://en.wikipedia.org/wiki/Timsort) algorithm whose space complexity is $\mathcal{O}(N)$ 82 | - In Java, the [Arrays. sort()](https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#sort-byte:A-) is implemented as a variant of quicksort algorithm whose space complexity is $\mathcal{O}(\log{N})$. 83 | 84 | ## Two City Scheduling(Medium #1029) 85 | 86 | **Question**: A company is planning to interview `2n` people. Given the array `costs` where `costs[i] = [aCosti, bCosti]`, the cost of flying the `ith` person to city `a` is `aCosti`, and the cost of flying the `ith` person to city `b` is `bCosti`. 87 | 88 | Return *the minimum cost to fly every person to a city* such that exactly `n` people arrive in each city. 89 | 90 | **Example 1:** 91 | 92 | ``` 93 | Input: costs = [[10,20],[30,200],[400,50],[30,20]] 94 | Output: 110 95 | Explanation: 96 | The first person goes to city A for a cost of 10. 97 | The second person goes to city A for a cost of 30. 98 | The third person goes to city B for a cost of 50. 99 | The fourth person goes to city B for a cost of 20. 100 | 101 | The total minimum cost is 10 + 30 + 50 + 20 = 110 to have half the people interviewing in each city. 102 | ``` 103 | 104 | **Example 2:** 105 | 106 | ``` 107 | Input: costs = [[259,770],[448,54],[926,667],[184,139],[840,118],[577,469]] 108 | Output: 1859 109 | ``` 110 | 111 | **Example 3:** 112 | 113 | ``` 114 | Input: costs = [[515,563],[451,713],[537,709],[343,819],[855,779],[457,60],[650,359],[631,42]] 115 | Output: 3086 116 | ``` 117 | 118 | **Constraints:** 119 | 120 | - `2 * n == costs.length` 121 | - `2 <= costs.length <= 100` 122 | - `costs.length` is even. 123 | - `1 <= aCosti, bCosti <= 1000` 124 | 125 | ### Standard Solution 126 | 127 | #### Solution #1 Greedy 128 | 129 | * Greedy problems usually look like "Find a minimum number of *something* to do *something*" or "Find a maximum number of *something* to fit in *some conditions*", and typically propose an unsorted input. 130 | * The idea of the greedy algorithm is to pick the *locally* optimal move at each step, that will lead to the *globally* optimal solution. 131 | 132 | * The standard solution has $\mathcal{O}(N \log N)$ time complexity and consists of two parts: 133 | - Figure out how to sort the input data ($\mathcal{O}(N \log N)$ time). That could be done directly by a sorting or indirectly by a heap usage. Typically sort is better than the heap usage because of gain in space. 134 | - Parse the sorted input to have a solution ($\mathcal{O}(N)$ time). 135 | * Sort the persons in the ascending order by `price_A - price_B` parameter, which indicates the company's additional costs(Based on the two group differences). 136 | * The first n persons choose group A, the last n persons choose group B. 137 | 138 | ```java 139 | public int twoCitySchedCost(int[][] costs) { 140 | // Sort by a gain which company has 141 | // by sending a person to city A and not to city B 142 | Arrays.sort(costs, new Comparator() { 143 | @Override 144 | public int compare(int[] o1, int[] o2) { 145 | return o1[0] - o1[1] - (o2[0] - o2[1]); 146 | } 147 | }); 148 | 149 | int total = 0; 150 | int n = costs.length / 2; 151 | // To optimize the company expenses, 152 | // send the first n persons to the city A 153 | // and the others to the city B 154 | for (int i = 0; i < n; ++i) total += costs[i][0] + costs[i + n][1]; 155 | return total; 156 | } 157 | ``` 158 | 159 | - Time complexity: $\mathcal{O}(N \log N)$ because of sorting of input data. 160 | - Space complexity: $\mathcal{O}(1)$ since it's a constant space solution. 161 | -------------------------------------------------------------------------------- /Hash Table/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Hash Table/.DS_Store -------------------------------------------------------------------------------- /Heap/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Heap/.DS_Store -------------------------------------------------------------------------------- /Heap/Heap Tutorials.md: -------------------------------------------------------------------------------- 1 | # Heap Tutorials 2 | 3 | ## Priority Queue 4 | 5 | * A priority queue is an abstract data type similar to a regular queue or stack data structure in which each element additionally has a "priority" associated with it. 6 | * In a priority queue, an element with high priority is served before an element with low priority. 7 | * **Heap vs. PQ** 8 | * A priority queue is an abstract data type, while a Heap is a data structure. 9 | * Therefore, a Heap is not a Priority Queue, but a way to implement a Priority Queue. 10 | 11 | * **Implementation**: 12 | * There are multiple ways to implement a Priority Queue, such as array and linked list. However, these implementations only guarantee $O(1)$ the time complexity for either insertion or deletion, while the other operation will have a time complexity of $O(N)$. 13 | * On the other hand, implementing the priority queue with Heap will allow both insertion and deletion to have a time complexity of $O(\log N)$. 14 | 15 | ## Heap 16 | 17 | * A **Heap** is a special type of binary tree. A heap is a binary tree that meets the following criteria: 18 | * Is a **complete binary tree**; 19 | * The value of each node must be **no greater than (or no less than)** the value of its child nodes. 20 | 21 | * A Heap has the following properties: 22 | 23 | - Insertion of an element into the Heap has a time complexity of $O(\log N)$; 24 | 25 | - Deletion of an element from the Heap has a time complexity of $O(\log N)$; 26 | 27 | - The maximum/minimum value in the Heap can be obtained with $O(1)$ time complexity. 28 | 29 | ### Heap Classification 30 | 31 | There are two kinds of heaps: **Max Heap** and **Min Heap**. 32 | 33 | - Max Heap: Each node in the Heap has a value **no less than** its child nodes. Therefore, the top element (root node) has the **largest** value in the Heap. 34 | - Min Heap: Each node in the Heap has a value **no larger than** its child nodes. Therefore, the top element (root node) has the **smallest** value in the Heap. 35 | 36 | img 37 | 38 | * [Heap Insertion](https://leetcode.com/explore/learn/card/heap/643/heap/4019/) 39 | * [Heap Deletion](https://leetcode.com/explore/learn/card/heap/643/heap/4020/) 40 | 41 | ### Implementation of a Heap 42 | 43 | * Use an array to implement a heap 44 | 45 | * Min Heap 46 | 47 | ```java 48 | // Implementing "Min Heap" 49 | public class MinHeap { 50 | // Create a complete binary tree using an array 51 | // Then use the binary tree to construct a Heap 52 | int[] minHeap; 53 | // the number of elements is needed when instantiating an array 54 | // heapSize records the size of the array 55 | int heapSize; 56 | // realSize records the number of elements in the Heap 57 | int realSize = 0; 58 | 59 | public MinHeap(int heapSize) { 60 | this.heapSize = heapSize; 61 | minHeap = new int[heapSize + 1]; 62 | // To better track the indices of the binary tree, 63 | // we will not use the 0-th element in the array 64 | // You can fill it with any value 65 | minHeap[0] = 0; 66 | } 67 | 68 | // Function to add an element 69 | public void add(int element) { 70 | realSize++; 71 | // If the number of elements in the Heap exceeds the preset heapSize 72 | // print "Added too many elements" and return 73 | if (realSize > heapSize) { 74 | System.out.println("Added too many elements!"); 75 | realSize--; 76 | return; 77 | } 78 | // Add the element into the array 79 | minHeap[realSize] = element; 80 | // Index of the newly added element 81 | int index = realSize; 82 | // Parent node of the newly added element 83 | // Note if we use an array to represent the complete binary tree 84 | // and store the root node at index 1 85 | // index of the parent node of any node is [index of the node / 2] 86 | // index of the left child node is [index of the node * 2] 87 | // index of the right child node is [index of the node * 2 + 1] 88 | int parent = index / 2; 89 | // If the newly added element is smaller than its parent node, 90 | // its value will be exchanged with that of the parent node 91 | while ( minHeap[index] < minHeap[parent] && index > 1 ) { 92 | int temp = minHeap[index]; 93 | minHeap[index] = minHeap[parent]; 94 | minHeap[parent] = temp; 95 | index = parent; 96 | parent = index / 2; 97 | } 98 | } 99 | 100 | // Get the top element of the Heap 101 | public int peek() { 102 | return minHeap[1]; 103 | } 104 | 105 | // Delete the top element of the Heap 106 | public int pop() { 107 | // If the number of elements in the current Heap is 0, 108 | // print "Don't have any elements" and return a default value 109 | if (realSize < 1) { 110 | System.out.println("Don't have any element!"); 111 | return Integer.MAX_VALUE; 112 | } else { 113 | // When there are still elements in the Heap 114 | // realSize >= 1 115 | int removeElement = minHeap[1]; 116 | // Put the last element in the Heap to the top of Heap 117 | minHeap[1] = minHeap[realSize]; 118 | realSize--; 119 | int index = 1; 120 | // When the deleted element is not a leaf node 121 | while (index <= realSize / 2) { 122 | // the left child of the deleted element 123 | int left = index * 2; 124 | // the right child of the deleted element 125 | int right = (index * 2) + 1; 126 | // If the deleted element is larger than the left or right child 127 | // its value needs to be exchanged with the smaller value 128 | // of the left and right child 129 | if (minHeap[index] > minHeap[left] || minHeap[index] > minHeap[right]) { 130 | if (minHeap[left] < minHeap[right]) { 131 | int temp = minHeap[left]; 132 | minHeap[left] = minHeap[index]; 133 | minHeap[index] = temp; 134 | index = left; 135 | } else { 136 | // maxHeap[left] >= maxHeap[right] 137 | int temp = minHeap[right]; 138 | minHeap[right] = minHeap[index]; 139 | minHeap[index] = temp; 140 | index = right; 141 | } 142 | } else { 143 | break; 144 | } 145 | } 146 | return removeElement; 147 | } 148 | } 149 | 150 | // return the number of elements in the Heap 151 | public int size() { 152 | return realSize; 153 | } 154 | 155 | public String toString() { 156 | if (realSize == 0) { 157 | return "No element!"; 158 | } else { 159 | StringBuilder sb = new StringBuilder(); 160 | sb.append('['); 161 | for (int i = 1; i <= realSize; i++) { 162 | sb.append(minHeap[i]); 163 | sb.append(','); 164 | } 165 | sb.deleteCharAt(sb.length() - 1); 166 | sb.append(']'); 167 | return sb.toString(); 168 | } 169 | } 170 | 171 | public static void main(String[] args) { 172 | // Test case 173 | MinHeap minHeap = new MinHeap(3); 174 | minHeap.add(3); 175 | minHeap.add(1); 176 | minHeap.add(2); 177 | // [1,3,2] 178 | System.out.println(minHeap.toString()); 179 | // 1 180 | System.out.println(minHeap.peek()); 181 | // 1 182 | System.out.println(minHeap.pop()); 183 | // [2, 3] 184 | System.out.println(minHeap.toString()); 185 | minHeap.add(4); 186 | // Add too many elements 187 | minHeap.add(5); 188 | // [2,3,4] 189 | System.out.println(minHeap.toString()); 190 | } 191 | } 192 | 193 | ``` 194 | 195 | * Max Heap 196 | 197 | ```java 198 | // Implementing "Max Heap" 199 | public class MaxHeap { 200 | // Create a complete binary tree using an array 201 | // Then use the binary tree to construct a Heap 202 | int[] maxHeap; 203 | // the number of elements is needed when instantiating an array 204 | // heapSize records the size of the array 205 | int heapSize; 206 | // realSize records the number of elements in the Heap 207 | int realSize = 0; 208 | 209 | public MaxHeap(int heapSize) { 210 | this.heapSize = heapSize; 211 | maxHeap = new int[heapSize + 1]; 212 | // To better track the indices of the binary tree, 213 | // we will not use the 0-th element in the array 214 | // You can fill it with any value 215 | maxHeap[0] = 0; 216 | } 217 | 218 | // Function to add an element 219 | public void add(int element) { 220 | realSize++; 221 | // If the number of elements in the Heap exceeds the preset heapSize 222 | // print "Added too many elements" and return 223 | if (realSize > heapSize) { 224 | System.out.println("Added too many elements!"); 225 | realSize--; 226 | return; 227 | } 228 | // Add the element into the array 229 | maxHeap[realSize] = element; 230 | // Index of the newly added element 231 | int index = realSize; 232 | // Parent node of the newly added element 233 | // Note if we use an array to represent the complete binary tree 234 | // and store the root node at index 1 235 | // index of the parent node of any node is [index of the node / 2] 236 | // index of the left child node is [index of the node * 2] 237 | // index of the right child node is [index of the node * 2 + 1] 238 | 239 | int parent = index / 2; 240 | // If the newly added element is larger than its parent node, 241 | // its value will be exchanged with that of the parent node 242 | while ( maxHeap[index] > maxHeap[parent] && index > 1 ) { 243 | int temp = maxHeap[index]; 244 | maxHeap[index] = maxHeap[parent]; 245 | maxHeap[parent] = temp; 246 | index = parent; 247 | parent = index / 2; 248 | } 249 | } 250 | 251 | // Get the top element of the Heap 252 | public int peek() { 253 | return maxHeap[1]; 254 | } 255 | 256 | // Delete the top element of the Heap 257 | public int pop() { 258 | // If the number of elements in the current Heap is 0, 259 | // print "Don't have any elements" and return a default value 260 | if (realSize < 1) { 261 | System.out.println("Don't have any element!"); 262 | return Integer.MIN_VALUE; 263 | } else { 264 | // When there are still elements in the Heap 265 | // realSize >= 1 266 | int removeElement = maxHeap[1]; 267 | // Put the last element in the Heap to the top of Heap 268 | maxHeap[1] = maxHeap[realSize]; 269 | realSize--; 270 | int index = 1; 271 | // When the deleted element is not a leaf node 272 | while (index <= realSize / 2) { 273 | // the left child of the deleted element 274 | int left = index * 2; 275 | // the right child of the deleted element 276 | int right = (index * 2) + 1; 277 | // If the deleted element is smaller than the left or right child 278 | // its value needs to be exchanged with the larger value 279 | // of the left and right child 280 | if (maxHeap[index] < maxHeap[left] || maxHeap[index] < maxHeap[right]) { 281 | if (maxHeap[left] > maxHeap[right]) { 282 | int temp = maxHeap[left]; 283 | maxHeap[left] = maxHeap[index]; 284 | maxHeap[index] = temp; 285 | index = left; 286 | } else { 287 | // maxHeap[left] <= maxHeap[right] 288 | int temp = maxHeap[right]; 289 | maxHeap[right] = maxHeap[index]; 290 | maxHeap[index] = temp; 291 | index = right; 292 | } 293 | } else { 294 | break; 295 | } 296 | } 297 | return removeElement; 298 | } 299 | } 300 | 301 | // return the number of elements in the Heap 302 | public int size() { 303 | return realSize; 304 | } 305 | 306 | public String toString() { 307 | if (realSize == 0) { 308 | return "No element!"; 309 | } else { 310 | StringBuilder sb = new StringBuilder(); 311 | sb.append('['); 312 | for (int i = 1; i <= realSize; i++) { 313 | sb.append(maxHeap[i]); 314 | sb.append(','); 315 | } 316 | sb.deleteCharAt(sb.length() - 1); 317 | sb.append(']'); 318 | return sb.toString(); 319 | } 320 | } 321 | 322 | public static void main(String[] args) { 323 | // Test case 324 | MaxHeap maxheap = new MaxHeap(5); 325 | maxheap.add(1); 326 | maxheap.add(2); 327 | maxheap.add(3); 328 | // [3,1,2] 329 | System.out.println(maxheap.toString()); 330 | // 3 331 | System.out.println(maxheap.peek()); 332 | // 3 333 | System.out.println(maxheap.pop()); 334 | System.out.println(maxheap.pop()); 335 | System.out.println(maxheap.pop()); 336 | // No element 337 | System.out.println(maxheap.toString()); 338 | maxheap.add(4); 339 | // Add too many elements 340 | maxheap.add(5); 341 | // [4,1,2] 342 | System.out.println(maxheap.toString()); 343 | } 344 | } 345 | 346 | ``` 347 | 348 | ## Heap Construction 349 | 350 | * When creating a Heap, we can simultaneously perform the **heapify** operation. Heapify means converting a group of data into a Heap. 351 | * Time complexity: $O(N)$. 352 | * Space complexity: $O(N)$. 353 | 354 | ```java 355 | // In Java, a Heap is represented by a Priority Queue 356 | import java.util.Collections; 357 | import java.util.PriorityQueue; 358 | import java.util.Arrays; 359 | 360 | // Construct an empty Min Heap 361 | PriorityQueue minHeap = new PriorityQueue<>(); 362 | 363 | // Construct an empty Max Heap 364 | PriorityQueue maxHeap = new PriorityQueue<>(Collections.reverseOrder()); 365 | 366 | // Construct a Heap with initial elements. 367 | // This process is named "Heapify". 368 | // The Heap is a Min Heap 369 | PriorityQueue heapWithValues= new PriorityQueue<>(Arrays.asList(3, 1, 2)); 370 | ``` 371 | 372 | * Inserting an element 373 | * Time complexity: $O(\log N)$ 374 | * Space complexity: $O(1)$ 375 | 376 | ```java 377 | // Insert an element to the Min Heap 378 | minHeap.add(5); 379 | 380 | // Insert an element to the Max Heap 381 | maxHeap.add(5); 382 | ``` 383 | 384 | * Getting the top element of the heap 385 | * Time complexity: $O(1)$ 386 | * Space complexity: $O(1)$ 387 | 388 | ```java 389 | // Get top element from the Min Heap 390 | // i.e. the smallest element 391 | minHeap.peek(); 392 | // Get top element from the Max Heap 393 | // i.e. the largest element 394 | maxHeap.peek(); 395 | ``` 396 | 397 | * Deleting the top element 398 | * Time complexity: $O(\log N)$ 399 | * Space complexity: $O(1)$ 400 | 401 | ```java 402 | // Delete top element from the Min Heap 403 | minHeap.poll(); 404 | 405 | // Delete top element from the Max Heap 406 | maxheap.poll(); 407 | ``` 408 | 409 | * Getting the length of a heap 410 | * Time complexity: $O(1)$ 411 | * Space complexity: $O(1)$ 412 | 413 | ```java 414 | // Length of the Min Heap 415 | minHeap.size(); 416 | 417 | // Length of the Max Heap 418 | maxHeap.size(); 419 | 420 | // Note, in Java, apart from checking if the length of the Heap is 0, we can also use isEmpty() 421 | // If there are no elements in the Heap, isEmpty() will return true; 422 | // If there are still elements in the Heap, isEmpty() will return false; 423 | ``` 424 | 425 | * Time and Space Complexity: 426 | 427 | | Heap method | Time complexity | Space complexity | 428 | | ---------------------- | --------------- | ---------------- | 429 | | Construct a Heap | $O(N)$ | $O(N)$ | 430 | | Insert an element | $O(\log N)$ | $O(1)$ | 431 | | Get the top element | $O(1)$ | $O(1)$ | 432 | | Delete the top element | $O(\log N)$ | $O(1)$ | 433 | | Get the size of a Heap | $O(1)$ | $O(1)$ | 434 | 435 | ## Common Methods of Heap 436 | 437 | **Min Heap** 438 | 439 | ```java 440 | // Code for Min Heap 441 | import java.util.PriorityQueue; 442 | 443 | public class App { 444 | public static void main(String[] args) { 445 | // Construct an instance of Min Heap 446 | PriorityQueue minHeap = new PriorityQueue<>(); 447 | 448 | // Add 3,1,2 respectively to the Min Heap 449 | minHeap.add(3); 450 | minHeap.add(1); 451 | minHeap.add(2); 452 | 453 | // Check all elements in the Min Heap, the result is [1, 3, 2] 454 | System.out.println("minHeap: " + minHeap.toString()); 455 | 456 | // Get the top element of the Min Heap 457 | int peekNum = minHeap.peek(); 458 | 459 | // The result is 1 460 | System.out.println("peek number: " + peekNum); 461 | 462 | // Delete the top element in the Min Heap 463 | int pollNum = minHeap.poll(); 464 | 465 | // The reult is 1 466 | System.out.println("poll number: " + pollNum); 467 | 468 | // Check the top element after deleting 1, the result is 2 469 | System.out.println("peek number: " + minHeap.peek()); 470 | 471 | // Check all elements in the Min Heap, the result is [2,3] 472 | System.out.println("minHeap: " + minHeap.toString()); 473 | 474 | // Check the number of elements in the Min Heap 475 | // Which is also the length of the Min Heap 476 | int heapSize = minHeap.size(); 477 | 478 | // The result is 2 479 | System.out.println("minHeap size: " + heapSize); 480 | 481 | // Check if the Min Heap is empty 482 | boolean isEmpty = minHeap.isEmpty(); 483 | 484 | // The result is false 485 | System.out.println("isEmpty: " + isEmpty); 486 | } 487 | } 488 | ``` 489 | 490 | **Max Heap** 491 | 492 | ```java 493 | // Code for Max Heap 494 | import java.util.Collections; 495 | import java.util.PriorityQueue; 496 | 497 | public class App { 498 | public static void main(String[] args) { 499 | // Construct an instance of Max Heap 500 | PriorityQueue maxHeap = new PriorityQueue<>(Collections.reverseOrder()); 501 | 502 | // Add 1,3,2 respectively to the Max Heap 503 | maxHeap.add(1); 504 | maxHeap.add(3); 505 | maxHeap.add(2); 506 | 507 | // Check all elements in the Max Heap, the result is [3, 1, 2] 508 | System.out.println("maxHeap: " + maxHeap.toString()); 509 | 510 | // Get the top element of the Max Heap 511 | int peekNum = maxHeap.peek(); 512 | 513 | // The result is 3 514 | System.out.println("peek number: " + peekNum); 515 | 516 | // Delete the top element in the Max Heap 517 | int pollNum = maxHeap.poll(); 518 | 519 | // The reult is 3 520 | System.out.println("poll number: " + pollNum); 521 | 522 | // Check the top element after deleting 3, the result is 2 523 | System.out.println("peek number: " + maxHeap.peek()); 524 | 525 | // Check all elements in the Max Heap, the result is [2,1] 526 | System.out.println("maxHeap: " + maxHeap.toString()); 527 | 528 | // Check the number of elements in the Max Heap 529 | // Which is also the length of the Max Heap 530 | int heapSize = maxHeap.size(); 531 | 532 | // The result is 2 533 | System.out.println("maxHeap size: " + heapSize); 534 | 535 | // Check if the Max Heap is empty 536 | boolean isEmpty = maxHeap.isEmpty(); 537 | 538 | // The result is false 539 | System.out.println("isEmpty: " + isEmpty); 540 | } 541 | } 542 | ``` 543 | 544 | ## Heap Sort 545 | 546 | The sorting algorithm using a **Min Heap** is as follows: 547 | 548 | 1. Heapify all elements into a Min Heap. 549 | 2. Record and delete the top element. 550 | 3. Put the top element into an array T that stores all sorted elements. Now, the Heap will remain a Min Heap. 551 | 4. Repeat steps 2 and 3 until the Heap is empty. The array T will contain all elements sorted in ascending order. 552 | 553 | The sorting algorithm using a **Max Heap** is as follows: 554 | 555 | 1. Heapify all elements into a Max Heap. 556 | 2. Record and delete the top element. 557 | 3. Put the top element into an array T that stores all sorted elements. Now, the Heap will remain a Max Heap. 558 | 4. Repeat steps 2 and 3 until the Heap is empty. The array T will contain all elements sorted in descending order. 559 | 560 | * **Complexity Analysis:** 561 | * Let N be the total number of elements. 562 | * Time complexity: $O(N \log N)$ 563 | * Space complexity: $O(N)$ 564 | 565 | ## The Top K Problem 566 | 567 | Solution of the Top K largest elements: 568 | 569 | 1. Construct a Max Heap. 570 | 2. Add all elements into the Max Heap. 571 | 3. Traversing and deleting the top element (using pop() or poll() for instance), and storing the value into the result array T. 572 | 4. Repeat step 3 until we have removed the K largest elements. 573 | 574 | **Complexity Analysis** 575 | 576 | * Time complexity: $O(K \log N + N)$ 577 | * Steps one and two require us to construct a Max Heap which requires $O(N)$ time using the previously discussed heapify method. Each element removed from the heap requires $O(\log N)$ time; this process is repeated K*K* times. Thus the total time complexity is $O(K \log N + N)$. 578 | 579 | * Space complexity: $O(N)$ 580 | * After step 2, the heap will store all N elements. 581 | 582 | ## The K-th Element 583 | 584 | ### Approach 1 585 | 586 | * Solution of the K-th largest element: 587 | 588 | 1. Construct a Max Heap. 589 | 2. Add all elements into the Max Heap. 590 | 3. Traversing and deleting the top element (using pop() or poll() for instance). 591 | 4. Repeat Step 3 K times until we find the K-th largest element. 592 | 593 | * Let N be the total number of elements. 594 | 595 | Time complexity: $O(K \log N + N)$ 596 | 597 | - Steps one and two require us to construct a Max Heap which requires O(N) time using the previously discussed heapify method. Each element removed from the heap requires $O(\log N)$ time; this process is repeated K times. Thus the total time complexity is $O(K \log N + N)$. 598 | 599 | Space complexity: $O(N)$ 600 | 601 | - After step 2, the heap will store all N elements. 602 | 603 | ### Approach 2 604 | 605 | * Solution of the K-th largest element: 606 | 607 | 1. Construct a Min Heap with size K. 608 | 2. Add elements to the Min Heap one by one. 609 | 3. When there are K elements in the “Min Heap”, compare the current element with the top element of the Heap: 610 | - If the current element is not larger than the top element of the Heap, drop it and proceed to the next element. 611 | - If the current element is larger than the Heap’s top element, delete the Heap’s top element, and add the current element to the “Min Heap”. 612 | 4. Repeat Steps 2 and 3 until all elements have been iterated. 613 | 614 | Now the top element in the Min Heap is the K-th largest element. 615 | 616 | * Complexity: 617 | 618 | * Time complexity: $O(N \log K)$ 619 | 620 | - Steps one and two will require $O(K \log K)$ time if the elements are added one by one to the heap, however using the heapify method, these two steps could be accomplished in $O(K)$ time. Steps 3 and 4 will require $O(\log K)$ time each time an element must be replaced in the heap. In the worst-case scenario, this will be done N - K times. Thus the total time complexity is $O((N - K) \log K + K \log K)$ which simplifies to $O(N \log K)$. 621 | 622 | Space complexity: $O(K)$ 623 | 624 | - The heap will contain at most K elements at any given time. 625 | 626 | -------------------------------------------------------------------------------- /LeeCode Summary.xlsx.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/LeeCode Summary.xlsx.xlsx -------------------------------------------------------------------------------- /Math/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Math/.DS_Store -------------------------------------------------------------------------------- /OA Summary/Online Assessment.md: -------------------------------------------------------------------------------- 1 | # Online Assessment 2 | 3 | ## Square Data Engineering Intern 4 | 5 | **Source**: [Credit to this link and author](https://leetcode.com/discuss/interview-question/1014660/samsara-oa-codesignal-summer-internship) 6 | 7 | #### Problem 8 | 9 | ``` 10 | You have a passage of text that needs to be typed out, but some of the letter keys on your keyboard are broken! You are given an array letters representing the working letter keys, as well as a string text, and your task is to determine how many of the words from text can be typed using the broken keyboard. It is guaranteed that all of the non-letter keys are working (including all punctuation and special characters). 11 | A word is defined as a sequence of consecutive characters which does not contain any spaces. 12 | The given text is a string consisting of words, each separated by exactly one space. 13 | It is guaranteed that text does not contain any leading or trailing spaces. 14 | Note that the characters in letters are all lowercase, but since the shift key is working, 15 | it's possible to type the uppercase versions also. 16 | ``` 17 | 18 | ``` 19 | Example: 20 | 1. For text = "Hello, this is CodeSignal!" and letters = ['e', 'i', 'h', 'l', 'o', 's'], 21 | the output should be brokenKeyboard(text, letters) = 2. 22 | - "Hello," can be typed since the characters 'h', 'e', 'l' and 'o' are in letters. 23 | - Note that the symbol ',' also belongs to the current word and can by typed. 24 | - "this" cannot be typed because the character 't' is not in letters. 25 | - "is" can be typed (both 'i' and 's' appear in letters). 26 | - "CodeSignal!" cannot be typed because the character 'c' is not in letters (as well as 'd', 'g', 'n', and 'a'). 27 | 2. For text = "Hi, this is Chris!" and letters = ['r', 's', 't', 'c', 'h'], the output should be 28 | brokenKeyboard(text, letters) = 0. 29 | - Each word contains the character 'i' which does not appear in letters and thus cannot be typed on the keyboard. 30 | 3. For text = "3 + 2 = 5" and letters = [], the output should be brokenKeyboard(text, letters) = 5. 31 | - There are no working letters on the keyboard, but since each of these words consists of numbers and 32 | special characters, they can all be typed, and there are 5 of them. 33 | ``` 34 | 35 | **Solution**: 36 | 37 | ```java 38 | public static int checkBrokenLetter(String text, char[] letters) { 39 | // Build hashSet 40 | Set set = new HashSet<>(); 41 | for (char c : letters) { 42 | set.add(c); 43 | } 44 | String[] words= text.split(" "); 45 | int count = 0; 46 | for (String word: words) { 47 | // important to learn how to clean the string, regular expression 48 | String cleanText = word.replaceAll("[^[a-zA-Z]]", "").toLowerCase(); 49 | System.out.println(cleanText); 50 | boolean valid = true; 51 | for (int i = 0; i < cleanText.length(); i++) { 52 | if (! set.contains(cleanText.charAt(i))) { 53 | valid = false; 54 | break; 55 | } 56 | } 57 | if (valid) { 58 | count++; 59 | } 60 | } 61 | return count; 62 | } 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /Queue & Stack/Queue Problems Part #1.md: -------------------------------------------------------------------------------- 1 | # Queue Problems Part #1 2 | 3 | ## Design Hit Counter(Medium #362) 4 | 5 | **Question**: Design a hit counter which counts the number of hits received in the past `5` minutes (i.e., the past `300` seconds). 6 | 7 | Your system should accept a `timestamp` parameter (**in seconds** granularity), and you may assume that calls are being made to the system in chronological order (i.e., `timestamp` is monotonically increasing). Several hits may arrive roughly at the same time. 8 | 9 | Implement the `HitCounter` class: 10 | 11 | - `HitCounter()` Initializes the object of the hit counter system. 12 | - `void hit(int timestamp)` Records a hit that happened at `timestamp` (**in seconds**). Several hits may happen at the same `timestamp`. 13 | - `int getHits(int timestamp)` Returns the number of hits in the past 5 minutes from `timestamp` (i.e., the past `300` seconds). 14 | 15 | **Example 1:** 16 | 17 | ``` 18 | Input 19 | ["HitCounter", "hit", "hit", "hit", "getHits", "hit", "getHits", "getHits"] 20 | [[], [1], [2], [3], [4], [300], [300], [301]] 21 | Output 22 | [null, null, null, null, 3, null, 4, 3] 23 | 24 | Explanation 25 | HitCounter hitCounter = new HitCounter(); 26 | hitCounter.hit(1); // hit at timestamp 1. 27 | hitCounter.hit(2); // hit at timestamp 2. 28 | hitCounter.hit(3); // hit at timestamp 3. 29 | hitCounter.getHits(4); // get hits at timestamp 4, return 3. 30 | hitCounter.hit(300); // hit at timestamp 300. 31 | hitCounter.getHits(300); // get hits at timestamp 300, return 4. 32 | hitCounter.getHits(301); // get hits at timestamp 301, return 3. 33 | ``` 34 | 35 | **Constraints:** 36 | 37 | - `1 <= timestamp <= 2 * 109` 38 | - All the calls are being made to the system in chronological order (i.e., `timestamp` is monotonically increasing). 39 | - At most `300` calls will be made to `hit` and `getHits`. 40 | 41 | ### My Solution 42 | 43 | ```java 44 | class HitCounter { 45 | private LinkedList hitQueue; 46 | 47 | public HitCounter() { 48 | hitQueue = new LinkedList(); 49 | } 50 | public void hit(int timestamp) { 51 | this.hitQueue.addLast(timestamp); 52 | } 53 | /** return number of hits in the past 5 mins**/ 54 | private int findHitsInPast5Min(int pastTime){ 55 | 56 | while(this.hitQueue.peekFirst() != null){ 57 | if (this.hitQueue.getFirst() <= pastTime){ 58 | this.hitQueue.removeFirst(); 59 | } 60 | else break; 61 | } 62 | return this.hitQueue.size(); 63 | } 64 | public int getHits(int timestamp) { 65 | int pastTime = timestamp - 300; 66 | return findHitsInPast5Min(pastTime); 67 | } 68 | } 69 | 70 | /** 71 | * Your HitCounter object will be instantiated and called as such: 72 | * HitCounter obj = new HitCounter(); 73 | * obj.hit(timestamp); 74 | * int param_2 = obj.getHits(timestamp); 75 | */ 76 | ``` 77 | 78 | ### Standard Solution 79 | 80 | #### Solution #1 Queue 81 | 82 | * Similar to my solution: all the timestamps that we will encounter are going to be in increasing order. 83 | * Should be clean of API doc comments 84 | * This is the case of considering the elements in the FIFO manner (First in first out) which is best solved by using the "queue" data structure. 85 | 86 | ```java 87 | class HitCounter{ 88 | private Queue hits; 89 | 90 | /** Initialize your data structure here.**/ 91 | public HitCounter(){ 92 | this.hits = new LinkedList(); 93 | } 94 | 95 | /** 96 | * Record a hit 97 | * @param timestamp 98 | */ 99 | public void hit(int timestamp){ 100 | this.hits.add(timestamp); 101 | } 102 | 103 | /** 104 | * Return the number of hits in the past 5 minutes 105 | * @param timestamp 106 | */ 107 | public int getHits(int timestamp){ 108 | while (!this.hits.isEmpty()){ 109 | int diff = timestamp - this.hits.peek(); 110 | if (diff >= 300) this.hits.remove(); 111 | else break; 112 | } 113 | return this.hits.size(); 114 | } 115 | } 116 | ``` 117 | 118 | * Time complexity: `hit` is $O(1)$, while `getHits` is $O(N)$. 119 | * Space complexity: The queue can have up to N elements, hence overall space complexity of the approach is $O(N)$ 120 | 121 | #### Solution #2 Using Deque with Pairs 122 | 123 | * Similar to the solution 1 but using deque 124 | * Similar to my solution 125 | * Avoid this repetitive removals of the same value 126 | * If we encounter the hit for the same timestamp, instead of appending a new entry in the deque, we simply increment the count of the latest timestamp. 127 | 128 | ```java 129 | class HitCounter{ 130 | private int total; 131 | private Deque> hits; 132 | 133 | /** Initialize your data structure here.**/ 134 | public HitCounter(){ 135 | // Initialize total to 0 136 | this.total = 0; 137 | this.hits = new LinkedList>(); 138 | } 139 | 140 | /** 141 | * Record a hit 142 | * @param timestamp 143 | */ 144 | public void hit(int timestamp){ 145 | if (this.hits.isEmpty() || this.hits.getLast().getKey() != timestamp){ 146 | // Insert the new timestamp with count = 1 147 | this.hits.add(new Pair(timestamp, 1)); 148 | } 149 | else { 150 | // Update the count of latest timestamp by incrementing the count by 1 151 | // Obtain the current count of the lastest timestamp 152 | int prevCount = this.hits.getLast().getValue(); 153 | // Remove the last pair of (timestamp, count) from the deque 154 | this.hits.removeLast(); 155 | // Insert a new pair of (timestamp, updated count) in the deque 156 | this.hits.add(new Pair(timestamp, prevCount + 1)); 157 | } 158 | // Increment total 159 | this.total++; 160 | } 161 | 162 | /** 163 | * Return the number of hits in the past 5 minutes 164 | * @param timestamp 165 | */ 166 | public int getHits(int timestamp){ 167 | while (!this.hits.isEmpty()){ 168 | int diff = timestamp - this.hits.getFirst().getKey(); 169 | if (diff >= 300){ 170 | // Decrement total by the count of the oldest timestamp 171 | this.total -= this.hits.getFirst().getValue(); 172 | this.hits.removeFirst(); 173 | } 174 | else break; 175 | } 176 | return this.total; 177 | } 178 | } 179 | ``` 180 | 181 | - Time Complexity: 182 | - `hit` - $O(1)$. 183 | - `getHits` - If there are a total of n pairs present in the deque, worst case time complexity can be $O(n)$. However, by clubbing all the timestamps with same value together, for the $i^{th} $ timestamp with k repetitions, the time complexity is $O(1)$ as here, instead of removing all those k repetitions, we only remove a single entry from the deque. 184 | - Space complexity: If there are a total of $N$ elements that we encountered throughout, the space complexity is $O(N)$ (similar to Approach 1). However, in the case of repetitions, the space required for storing those k values $O(1)$. 185 | 186 | ## Design Circular Queue (Medium #622) 187 | 188 | **Question**: Design your implementation of the circular queue. The circular queue is a linear data structure in which the operations are performed based on FIFO (First In First Out) principle, and the last position is connected back to the first position to make a circle. It is also called "Ring Buffer". 189 | 190 | One of the benefits of the circular queue is that we can make use of the spaces in front of the queue. In a normal queue, once the queue becomes full, we cannot insert the next element even if there is a space in front of the queue. But using the circular queue, we can use the space to store new values. 191 | 192 | Implementation of the `MyCircularQueue` class: 193 | 194 | - `MyCircularQueue(k)` Initializes the object with the size of the queue to be `k`. 195 | - `int Front()` Gets the front item from the queue. If the queue is empty, return `-1`. 196 | - `int Rear()` Gets the last item from the queue. If the queue is empty, return `-1`. 197 | - `boolean enQueue(int value)` Inserts an element into the circular queue. Return `true` if the operation is successful. 198 | - `boolean deQueue()` Deletes an element from the circular queue. Return `true` if the operation is successful. 199 | - `boolean isEmpty()` Checks whether the circular queue is empty or not. 200 | - `boolean isFull()` Checks whether the circular queue is full or not. 201 | 202 | You must solve the problem without using the built-in queue data structure in your programming language. 203 | 204 | **Example 1:** 205 | 206 | ``` 207 | Input 208 | ["MyCircularQueue", "enQueue", "enQueue", "enQueue", "enQueue", "Rear", "isFull", "deQueue", "enQueue", "Rear"] 209 | [[3], [1], [2], [3], [4], [], [], [], [4], []] 210 | Output 211 | [null, true, true, true, false, 3, true, true, true, 4] 212 | 213 | Explanation 214 | MyCircularQueue myCircularQueue = new MyCircularQueue(3); 215 | myCircularQueue.enQueue(1); // return True 216 | myCircularQueue.enQueue(2); // return True 217 | myCircularQueue.enQueue(3); // return True 218 | myCircularQueue.enQueue(4); // return False 219 | myCircularQueue.Rear(); // return 3 220 | myCircularQueue.isFull(); // return True 221 | myCircularQueue.deQueue(); // return True 222 | myCircularQueue.enQueue(4); // return True 223 | myCircularQueue.Rear(); // return 4 224 | ``` 225 | 226 | **Constraints:** 227 | 228 | - `1 <= k <= 1000` 229 | - `0 <= value <= 1000` 230 | - At most `3000` calls will be made to `enQueue`, `deQueue`, `Front`, `Rear`, `isEmpty`, and `isFull`. 231 | 232 | ### My Solution 233 | 234 | ```java 235 | class MyCircularQueue { 236 | private int[] queue; 237 | private int size; 238 | // two pointers to track the queue 239 | private int front; 240 | private int rear; 241 | private int capacity; 242 | 243 | public MyCircularQueue(int k) { 244 | queue = new int[k]; 245 | capacity = k; 246 | size = 0; 247 | front = 0; 248 | rear = 0; 249 | } 250 | 251 | public boolean enQueue(int value) { 252 | if (size >= capacity){ 253 | return false; 254 | } 255 | // queue is empty 256 | if (size == 0){ 257 | queue[rear] = value; 258 | } 259 | else { 260 | // in-case we pass the end or array(circular) 261 | rear = (rear + 1) % capacity; 262 | queue[rear] = value; 263 | } 264 | size++; 265 | return true; 266 | } 267 | 268 | public boolean deQueue() { 269 | if (size <= 0){ 270 | return false; 271 | } 272 | // only 1 element left in the queue 273 | if (rear == front){ 274 | queue[rear] = 0; 275 | } 276 | else { 277 | queue[front] = 0; 278 | front = (front + 1) % capacity; 279 | } 280 | size--; 281 | return true; 282 | } 283 | 284 | public int Front() { 285 | if (size <= 0){ 286 | return -1; 287 | } 288 | return queue[front]; 289 | } 290 | public int Rear() { 291 | if (size <= 0){ 292 | return -1; 293 | } 294 | return queue[rear]; 295 | } 296 | public boolean isEmpty() { 297 | return size == 0; 298 | } 299 | public boolean isFull() { 300 | return size == capacity; 301 | } 302 | } 303 | ``` 304 | 305 | * The solution works well; the space complexity is $O(N)$ and the time complexity is $O(1)$ 306 | * Two pointers solution is easy to find the front and rear 307 | 308 | ### Standard Solution 309 | 310 | #### Solution #1 Array 311 | 312 | * Similar idea but only using 1 pointer 313 | 314 | * A simplified code, don't need to clear the cell to 0, only left the new element cover the old element. 315 | 316 | ```java 317 | class MyCircularQueue { 318 | 319 | private int[] queue; 320 | private int headIndex; 321 | private int count; 322 | private int capacity; 323 | 324 | /** Initialize your data structure here. Set the size of the queue to be k. */ 325 | public MyCircularQueue(int k) { 326 | this.capacity = k; 327 | this.queue = new int[k]; 328 | this.headIndex = 0; 329 | this.count = 0; 330 | } 331 | 332 | /** Insert an element into the circular queue. Return true if the operation is successful. */ 333 | public boolean enQueue(int value) { 334 | if (this.count == this.capacity) 335 | return false; 336 | this.queue[(this.headIndex + this.count) % this.capacity] = value; 337 | this.count += 1; 338 | return true; 339 | } 340 | 341 | /** Delete an element from the circular queue. Return true if the operation is successful. */ 342 | public boolean deQueue() { 343 | if (this.count == 0) 344 | return false; 345 | this.headIndex = (this.headIndex + 1) % this.capacity; 346 | this.count -= 1; 347 | return true; 348 | } 349 | 350 | /** Get the front item from the queue. */ 351 | public int Front() { 352 | if (this.count == 0) 353 | return -1; 354 | return this.queue[this.headIndex]; 355 | } 356 | 357 | /** Get the last item from the queue. */ 358 | public int Rear() { 359 | if (this.count == 0) 360 | return -1; 361 | int tailIndex = (this.headIndex + this.count - 1) % this.capacity; 362 | return this.queue[tailIndex]; 363 | } 364 | 365 | /** Checks whether the circular queue is empty or not. */ 366 | public boolean isEmpty() { 367 | return (this.count == 0); 368 | } 369 | 370 | /** Checks whether the circular queue is full or not. */ 371 | public boolean isFull() { 372 | return (this.count == this.capacity); 373 | } 374 | } 375 | ``` 376 | 377 | - Time complexity: $\mathcal{O}(1)$. All of the methods in our circular data structure are of constant time complexity. 378 | - Space Complexity: $\mathcal{O}(N)$. The overall space complexity of the data structure is linear, where N is the pre-assigned capacity of the queue. *However, it is worth mentioning that the memory consumption of the data structure remains at its pre-assigned capacity during its entire life cycle.* 379 | 380 | ## Moving Average from Data Stream (Easy #346) 381 | 382 | **Question**: Given a stream of integers and a window size, calculate the moving average of all integers in the sliding window. 383 | 384 | Implement the `MovingAverage` class: 385 | 386 | - `MovingAverage(int size)` Initializes the object with the size of the window `size`. 387 | - `double next(int val)` Returns the moving average of the last `size` values of the stream. 388 | 389 | **Example 1:** 390 | 391 | ``` 392 | Input 393 | ["MovingAverage", "next", "next", "next", "next"] 394 | [[3], [1], [10], [3], [5]] 395 | Output 396 | [null, 1.0, 5.5, 4.66667, 6.0] 397 | 398 | Explanation 399 | MovingAverage movingAverage = new MovingAverage(3); 400 | movingAverage.next(1); // return 1.0 = 1 / 1 401 | movingAverage.next(10); // return 5.5 = (1 + 10) / 2 402 | movingAverage.next(3); // return 4.66667 = (1 + 10 + 3) / 3 403 | movingAverage.next(5); // return 6.0 = (10 + 3 + 5) / 3 404 | ``` 405 | 406 | **Constraints:** 407 | 408 | - `1 <= size <= 1000` 409 | - `-105 <= val <= 105` 410 | - At most `104` calls will be made to `next`. 411 | 412 | ### My Solution 413 | 414 | * Each time only keep the same size of elements in the list 415 | 416 | ```java 417 | class MovingAverage { 418 | 419 | private int size; 420 | private List list; 421 | 422 | public MovingAverage(int size) { 423 | this.size = size; 424 | list = new ArrayList<>(); 425 | } 426 | 427 | public double next(int val) { 428 | list.add(val); 429 | if (list.size() > size){ 430 | list.remove(0); 431 | } 432 | int denominator = list.size(); 433 | int sum = 0; 434 | for (int i = 0; i < denominator; i++){ 435 | int curr = list.get(i); 436 | sum += curr; 437 | } 438 | return (double)sum / (double)denominator; 439 | } 440 | } 441 | ``` 442 | 443 | ### Standard Solution 444 | 445 | #### Solution #1 Array or List 446 | 447 | ```java 448 | class MovingAverage { 449 | int size; 450 | List queue = new ArrayList(); 451 | public MovingAverage(int size) { 452 | this.size = size; 453 | } 454 | 455 | public double next(int val) { 456 | queue.add(val); 457 | // calculate the sum of the moving window 458 | int windowSum = 0; 459 | for(int i = Math.max(0, queue.size() - size); i < queue.size(); ++i) 460 | windowSum += (int)queue.get(i); 461 | 462 | return windowSum * 1.0 / Math.min(queue.size(), size); 463 | } 464 | } 465 | ``` 466 | 467 | - Time Complexity: $\mathcal{O}(N)$ where N is the size of the moving window since we need to retrieve N elements from the queue at each invocation of `next(val)` function. 468 | - Space Complexity: $\mathcal{O}(M)$, where M is the length of the queue which would grow at each invocation of the `next(val)` function. 469 | 470 | #### Solution #2 Deque 471 | 472 | * Each time add value to the sum, delete the value in the tail if exceed the specified size 473 | 474 | ```java 475 | class MovingAverage { 476 | int size, windowSum = 0, count = 0; 477 | Deque queue = new ArrayDeque(); 478 | 479 | public MovingAverage(int size) { 480 | this.size = size; 481 | } 482 | 483 | public double next(int val) { 484 | ++count; 485 | // calculate the new sum by shifting the window 486 | queue.add(val); 487 | int tail = count > size ? (int)queue.poll() : 0; 488 | 489 | windowSum = windowSum - tail + val; 490 | 491 | return windowSum * 1.0 / Math.min(size, count); 492 | } 493 | } 494 | ``` 495 | 496 | - Time Complexity: $\mathcal{O}(1)$, as we explained in intuition. 497 | - Space Complexity: $\mathcal{O}(N)$, where N is the size of the moving window. 498 | 499 | ## Flatten Nested List Iterator (Medium #341) 500 | 501 | **Question**: You are given a nested list of integers `nestedList`. Each element is either an integer or a list whose elements may also be integers or other lists. Implement an iterator to flatten it. 502 | 503 | Implement the `NestedIterator` class: 504 | 505 | - `NestedIterator(List nestedList)` Initializes the iterator with the nested list `nestedList`. 506 | - `int next()` Returns the next integer in the nested list. 507 | - `boolean hasNext()` Returns `true` if there are still some integers in the nested list and `false` otherwise. 508 | 509 | Your code will be tested with the following pseudocode: 510 | 511 | ``` 512 | initialize iterator with nestedList 513 | res = [] 514 | while iterator.hasNext() 515 | append iterator.next() to the end of res 516 | return res 517 | ``` 518 | 519 | If `res` matches the expected flattened list, then your code will be judged as correct. 520 | 521 | **Example 1:** 522 | 523 | ``` 524 | Input: nestedList = [[1,1],2,[1,1]] 525 | Output: [1,1,2,1,1] 526 | Explanation: By calling next repeatedly until hasNext returns false, the order of elements returned by next should be: [1,1,2,1,1]. 527 | ``` 528 | 529 | **Example 2:** 530 | 531 | ``` 532 | Input: nestedList = [1,[4,[6]]] 533 | Output: [1,4,6] 534 | Explanation: By calling next repeatedly until hasNext returns false, the order of elements returned by next should be: [1,4,6]. 535 | ``` 536 | 537 | **Constraints:** 538 | 539 | - `1 <= nestedList.length <= 500` 540 | - The values of the integers in the nested list is in the range `[-106, 106]`. 541 | 542 | ### Standard Solution 543 | 544 | * Need to fully understand the `iterator`. `next()` would return the element 545 | * Flatten the elements into a list or other structure, then use `iterator` to iterate the structure. 546 | 547 | #### Solution #1 Recursion + List 548 | 549 | ```java 550 | import java.util.NoSuchElementException; 551 | public class NestedIterator implements Iterator { 552 | 553 | int curr; 554 | List list; 555 | 556 | public NestedIterator(List nestedList) { 557 | curr = 0; 558 | list = new ArrayList<>(); 559 | flattenList(nestedList); 560 | } 561 | 562 | // recursively unpacks a nest list in dfs 563 | // flatten all the elements to a list before the iterator begins 564 | private void flattenList(List nestedList){ 565 | for (NestedInteger nestedInteger : nestedList){ 566 | if (nestedInteger.isInteger()){ 567 | list.add(nestedInteger.getInteger()); 568 | } else { 569 | flattenList(nestedInteger.getList()); 570 | } 571 | } 572 | } 573 | 574 | @Override 575 | public Integer next() { 576 | if (!hasNext()) throw new NoSuchElementException(); 577 | return list.get(curr++); 578 | } 579 | 580 | @Override 581 | public boolean hasNext() { 582 | return curr < list.size(); 583 | } 584 | } 585 | ``` 586 | 587 | - Let N be the total number of *integers* within the nested list, L be the total number of *lists* within the nested list, and D be the maximum nesting depth (maximum number of lists inside each other). 588 | 589 | - Time complexity: 590 | 591 | We'll analyze each of the methods separately. 592 | 593 | - **Constructor:** $O(N + L)$ 594 | 595 | The constructor is where all the time-consuming work is done. 596 | 597 | For each list within the nested list, there will be one call to `flattenList(...)`. The loop within `flattenList(...)` will then iterate n times, where n is the number of integers within that list. Across all calls to `flattenList(...)`, there will be a total of N loop iterations. Therefore, the time complexity is the number of lists plus the number of integers, giving us $O(N + L)$ 598 | 599 | Notice that the maximum depth of the nesting does not impact the time complexity. 600 | 601 | - **next():** $O(1)$ 602 | 603 | Getting the next element requires incrementing `position` by 1 and accessing an element at a particular index of the `integers` list. Both of these are $O(1)$ operations. 604 | 605 | - **hasNext():** $O(1)$. 606 | 607 | Checking whether or not there is a next element requires comparing the length of the `integers` list to the `position` variable. This is an $O(1)$ operation. 608 | 609 | - Space complexity: $O(N + D)$. 610 | 611 | The most obvious auxiliary space is the `integers` list. The length of this is $O(N)$ 612 | 613 | The less obvious auxiliary space is the space used by the `flattenList(...)` function. Recall that recursive functions need to keep track of where they're up to by putting stack frames on the runtime stack. Therefore, we need to determine what the maximum number of stack frames there could be at a time is. Each time we encounter a nested list, we call `flattenList(...)` and a stack frame is added. Each time we finish processing a nested list, `flattenList(...)` returns and a stack frame is removed. Therefore, the maximum number of stack frames on the runtime stack is the maximum nesting depth, D. 614 | 615 | Because these two operations happen one-after-the-other, and either could be the largest, we add their time complexities together giving a final result of $O(N + D)$ 616 | 617 | #### Solution #2 Stack 618 | 619 | ```java 620 | import java.util.NoSuchElementException; 621 | 622 | public class NestedIterator implements Iterator { 623 | 624 | // In Java, the Stack class is considered deprecated. Best practice is to use 625 | // a Deque instead. We'll use addFirst() for push, and removeFirst() for pop. 626 | private Deque stack; 627 | 628 | public NestedIterator(List nestedList) { 629 | // The constructor puts them on in the order we require. No need to reverse. 630 | stack = new ArrayDeque(nestedList); 631 | } 632 | 633 | 634 | @Override 635 | public Integer next() { 636 | // As per java specs, throw an exception if there's no elements left. 637 | if (!hasNext()) throw new NoSuchElementException(); 638 | // hasNext ensures the stack top is now an integer. Pop and return 639 | // this integer. 640 | return stack.removeFirst().getInteger(); 641 | } 642 | 643 | 644 | @Override 645 | public boolean hasNext() { 646 | // Check if there are integers left by getting one onto the top of stack. 647 | makeStackTopAnInteger(); 648 | // If there are any integers remaining, one will be on the top of the stack, 649 | // and therefore the stack can't possibly be empty. 650 | return !stack.isEmpty(); 651 | } 652 | 653 | 654 | private void makeStackTopAnInteger() { 655 | // While there are items remaining on the stack and the front of 656 | // stack is a list (i.e. not integer), keep unpacking. 657 | while (!stack.isEmpty() && !stack.peekFirst().isInteger()) { 658 | // Put the NestedIntegers onto the stack in reverse order. 659 | List nestedList = stack.removeFirst().getList(); 660 | for (int i = nestedList.size() - 1; i >= 0; i--) { 661 | stack.addFirst(nestedList.get(i)); 662 | } 663 | } 664 | } 665 | } 666 | ``` 667 | 668 | - Time complexity. 669 | 670 | - **Constructor:** $O(N + L)$ 671 | 672 | The worst-case occurs when the initial input nestedList consists entirely of integers and empty lists (everything is in the top-level). In this case, every item is reversed and stored, giving a total time complexity of $O(N + L)$ 673 | 674 | - **makeStackTopAnInteger():** $O(\dfrac{L}{N})$or $O(1)$ 675 | 676 | If the top of the stack is an integer, then this function does nothing; taking $O(1)$ time. 677 | 678 | Otherwise, it needs to process the stack until an integer is on top. The best way of analyzing the time complexity is to look at the total cost across all calls to `makeStackTopAnInteger()` and then divide by the number of calls made. Once the iterator is exhausted `makeStackTopAnInteger()` must have seen every integer at least once, costing $O(N)$ time. Additionally, it has seen every list (except the first) on the stack at least once also, so this costs $O(L)$ time. Adding these together, we get $O(N + L)$ time. 679 | 680 | The amortized time of a single `makeStackTopAnInteger` is the total cost, $O(N + L)$, divided by the number of times it's called. In order to get all integers, we need to have called it N times. This gives us an amortized time complexity of $\dfrac{O(N + L)}{N} = O(\dfrac{N}{N} + \dfrac{L}{N}) = O(\dfrac{L}{N})$ 681 | 682 | - **next():** $O(\dfrac{L}{N})$ or $O(1)$ 683 | 684 | All of this method is $O(1)$, except for possibly the call to `makeStackTopAnInteger()`, giving us a time complexity the same as `makeStackTopAnInteger()`. 685 | 686 | - **hasNext():** $O(\dfrac{L}{N})$ or $O(1)$ 687 | 688 | All of these methods is $O(1)$, except for possibly the call to `makeStackTopAnInteger()`, giving us a time complexity the same as `makeStackTopAnInteger()`. 689 | 690 | - Space complexity: $O(N + L)$ 691 | 692 | In the worst case, where the top list contains N integers, or L empty lists, it will cost $O(N + L)$ space. Other expensive cases occur when the nesting is very deep. However, it's useful to remember that $D ≤ L$ (because each layer of nesting requires another list), and so we don't need to take this into account. 693 | 694 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeetCode Solution and Learning Notes 2 | 3 | ## Introduction 4 | Leetcode summary of solutions and tutorial notes. Java only. For self use. Categorized by topics. Each of the note includes my solution, tips and summary, and solutions provided by Leetcode. Some of the notes are tutorial. 5 | 6 | ## Catalog 7 | https://docs.google.com/spreadsheets/d/15wv-zRllN9pYZhOTVzVBLuY03gyqo_AH/edit?usp=sharing&ouid=101695934894145863901&rtpof=true&sd=true 8 | 9 | Please use the spreadsheet to find the location of the problems. The spreadsheet contains problem number, category, summary and my check dates. 10 | 11 | ## Update 12 | Usually I solve 3 problems everyday. The repo is updated in a daily basis. 13 | -------------------------------------------------------------------------------- /Recursion/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Recursion/.DS_Store -------------------------------------------------------------------------------- /Recursion/Recursion Problems Part #1.md: -------------------------------------------------------------------------------- 1 | # Recursion Problems Part #1 2 | 3 | ## Count Complete Tree Nodes(Medium #222) 4 | 5 | **Question**: Given the `root` of a **complete** binary tree, return the number of the nodes in the tree. 6 | 7 | According to **[Wikipedia](http://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees)**, every level, except possibly the last, is completely filled in a complete binary tree, and all nodes in the last level are as far left as possible. It can have between `1` and `2h` nodes inclusive at the last level `h`. 8 | 9 | Design an algorithm that runs in less than `O(n)` time complexity. 10 | 11 | 12 | 13 | **Example 1:** 14 | 15 | ![img](https://assets.leetcode.com/uploads/2021/01/14/complete.jpg) 16 | 17 | ``` 18 | Input: root = [1,2,3,4,5,6] 19 | Output: 6 20 | ``` 21 | 22 | **Example 2:** 23 | 24 | ``` 25 | Input: root = [] 26 | Output: 0 27 | ``` 28 | 29 | **Example 3:** 30 | 31 | ``` 32 | Input: root = [1] 33 | Output: 1 34 | ``` 35 | 36 | **Constraints:** 37 | 38 | - The number of nodes in the tree is in the range `[0, 5 * 104]`. 39 | - `0 <= Node.val <= 5 * 104` 40 | - The tree is guaranteed to be **complete**. 41 | 42 | ### My Solution 43 | 44 | * Recursion method to do the counting 45 | 46 | ```java 47 | public int countNodes(TreeNode root) { 48 | return count(root); 49 | } 50 | public int count(TreeNode root){ 51 | if (root == null){ 52 | return 0; 53 | } 54 | return 1 + count(root.left) + count(root.right); 55 | } 56 | ``` 57 | 58 | ### Standard Solution 59 | 60 | #### Solution #1 Linear Time 61 | 62 | * Same as my solution, but a more simplified version. 63 | 64 | ```java 65 | public int countNodes(TreeNode root){ 66 | return root != null ? 1 + countNodes(root.right) + countNodes(root.left) : 0; 67 | } 68 | ``` 69 | 70 | - Time complexity: $\mathcal{O}(N)$. 71 | - Space complexity: $\mathcal{O}(d) = \mathcal{O}(\log N)$ to keep the recursion stack, where d is a tree depth. -------------------------------------------------------------------------------- /SQL/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/SQL/.DS_Store -------------------------------------------------------------------------------- /Sort/Sorting Problems Part #1.md: -------------------------------------------------------------------------------- 1 | # Sorting Problems Part #1 2 | 3 | ## Sort List (Medium #148) 4 | 5 | **Question**: Given the `head` of a linked list, return *the list after sorting it in **ascending order***. 6 | 7 | **Example 1:** 8 | 9 | img 10 | 11 | ``` 12 | Input: head = [4,2,1,3] 13 | Output: [1,2,3,4] 14 | ``` 15 | 16 | **Example 2:** 17 | 18 | img 19 | 20 | ``` 21 | Input: head = [-1,5,3,4,0] 22 | Output: [-1,0,3,4,5] 23 | ``` 24 | 25 | **Example 3:** 26 | 27 | ``` 28 | Input: head = [] 29 | Output: [] 30 | ``` 31 | 32 | **Constraints:** 33 | 34 | - The number of nodes in the list is in the range `[0, 5 * 104]`. 35 | - `-105 <= Node.val <= 105` 36 | 37 | ### My Solution 38 | 39 | * Use an array list to record all the numbers, sort them using Collections 40 | * Then reassign them to the nodes 41 | 42 | ```java 43 | public ListNode sortList(ListNode head) { 44 | if (head == null){ 45 | return null; 46 | } 47 | List nums = new ArrayList<>(); 48 | ListNode current = head; 49 | while (current != null){ 50 | nums.add(current.val); 51 | current = current.next; 52 | } 53 | Collections.sort(nums); 54 | current = head; 55 | while(nums.size() > 0){ 56 | current.val = nums.remove(0); 57 | current = current.next; 58 | } 59 | return head; 60 | } 61 | ``` 62 | 63 | ### Standard Solution 64 | 65 | #### Solution #1 Top-down Merge Sort 66 | 67 | * Recursively split the list into two halves, until only one node in the list (find mid) 68 | * Merge two lists also in recursion 69 | 70 | ```java 71 | public ListNode sortList(ListNode head){ 72 | if (head == null || head.next == null){ 73 | return head; 74 | } 75 | ListNode mid = getMid(head); 76 | ListNode left = sortList(head); 77 | ListNode right = sortList(mid); 78 | return merge(left, right); 79 | } 80 | 81 | public ListNode merge(ListNode list1, ListNode list2){ 82 | ListNode dummyHead = new ListNode(); 83 | ListNode tail = dummyHead; 84 | while (list1 != null && list2 != null){ 85 | if { 86 | tail.next = list1; 87 | list1 = list1.next; 88 | tail = tail.next; 89 | } else { 90 | tail.next = list2; 91 | list2 = list2.next; 92 | tail = tail.next; 93 | } 94 | } 95 | tail.next = (list1 != null) ? list1 : list2; 96 | return dummyHead.next; 97 | } 98 | public ListNode getMid(ListNode head){ 99 | ListNode midPrev = null; 100 | while(head != null && head.next != null){ 101 | midPrev = (midPrev == null) ? head : midPrev.next; 102 | head = head.next.next; 103 | } 104 | ListNode mid = midPrev.next; 105 | midPrev.next = null; 106 | return mid; 107 | } 108 | ``` 109 | 110 | - Time Complexity: $\mathcal{O}(n \log n)$, where n is the number of nodes in the linked list. The algorithm can be split into 2 phases, Split and Merge. 111 | - Space Complexity: $\mathcal{O}(\log n)$, where n is the number of nodes in the linked list. Since the problem is recursive, we need additional space to store the recursive call stack. The maximum depth of the recursion tree is $\log n$ 112 | 113 | ## Largest Number (Medium #179) 114 | 115 | **Question**: Given a list of non-negative integers `nums`, arrange them such that they form the largest number and return it. 116 | 117 | Since the result may be very large, so you need to return a string instead of an integer. 118 | 119 | **Example 1:** 120 | 121 | ``` 122 | Input: nums = [10,2] 123 | Output: "210" 124 | ``` 125 | 126 | **Example 2:** 127 | 128 | ``` 129 | Input: nums = [3,30,34,5,9] 130 | Output: "9534330" 131 | ``` 132 | 133 | **Constraints:** 134 | 135 | - `1 <= nums.length <= 100` 136 | - `0 <= nums[i] <= 109` 137 | 138 | ### Standard Solution 139 | 140 | #### Solution #1 Sorting via Custom Comparator 141 | 142 | * Create a custom comparator, and compare the adding order (solve the problem: single digit vs. multiple digits) 143 | * Sort the string array 144 | * Add element to the string 145 | 146 | ```java 147 | private class LargeNumberComparator implements Comparator{ 148 | @Override 149 | public int compare(String a, String b){ 150 | String order1 = a + b; 151 | String order2 = b + a; 152 | return order2.compareTo(order1); 153 | } 154 | } 155 | 156 | public String largestNumber(int[] nums) { 157 | // get input integers as strings 158 | String[] strs = new String[nums.length]; 159 | for (int i = 0; i < nums.length; i++){ 160 | strs[i] = String.valueOf(nums[i]); 161 | } 162 | 163 | // sort strings according to custom comparator 164 | Arrays.sort(strs, new LargeNumberComparator()); 165 | 166 | // if, after being sorted, the largest number is 0, 167 | // the entire number is 0. 168 | if (strs[0].equals("0")) return "0"; 169 | 170 | // build largest number from sorted array 171 | String largestNumberStr = new String(); 172 | for (String numStr : strs){ 173 | largestNumberStr += numStr; 174 | } 175 | return largestNumberStr; 176 | } 177 | ``` 178 | 179 | ```java 180 | // almost same but much faster solution 181 | public String largestNumber(int[] nums) { 182 | if (nums == null || nums.length == 0) return ""; 183 | String[] strs = new String[nums.length]; 184 | for (int i = 0; i < nums.length; i++) { 185 | strs[i] = nums[i]+""; 186 | } 187 | Arrays.sort(strs, new Comparator() { 188 | @Override 189 | public int compare(String i, String j) { 190 | String s1 = i+j; 191 | String s2 = j+i; 192 | return s1.compareTo(s2); 193 | } 194 | }); 195 | if (strs[strs.length-1].charAt(0) == '0') return "0"; 196 | String res = new String(); 197 | for (int i = 0; i < strs.length; i++) { 198 | res = strs[i]+res; 199 | } 200 | return res; 201 | } 202 | ``` 203 | 204 | - Time complexity: $\mathcal{O}(nlgn)$ 205 | 206 | Although we are doing extra work in our comparator, it is only by a constant factor. Therefore, the overall runtime is dominated by the complexity of `sort`, which is $\mathcal{O}(nlgn)$ in Python and Java. 207 | 208 | - Space complexity: $\mathcal{O}(n)$ 209 | 210 | Here, we allocate $\mathcal{O}(n)$ additional space to store the copy of `nums`. Although we could do that work in place (if we decide that it is okay to modify `nums`), we must allocate $\mathcal{O}(n)$ space for the final return string. Therefore, the overall memory footprint is linear in length of `nums`. -------------------------------------------------------------------------------- /System Design/System Design Problem Part #1.md: -------------------------------------------------------------------------------- 1 | # System Design Problem Part #1 2 | 3 | ## Design Search Autocomplete System (Hard #642) 4 | 5 | **Question**: Design a search autocomplete system for a search engine. Users may input a sentence (at least one word and end with a special character `'#'`). 6 | 7 | You are given a string array `sentences` and an integer array `times` both of length `n` where `sentences[i]` is a previously typed sentence and `times[i]` is the corresponding number of times the sentence was typed. For each input character except `'#'`, return the top `3` historical hot sentences that have the same prefix as the part of the sentence already typed. 8 | 9 | Here are the specific rules: 10 | 11 | - The hot degree for a sentence is defined as the number of times a user typed the exactly same sentence before. 12 | - The returned top `3` hot sentences should be sorted by hot degree (The first is the hottest one). If several sentences have the same hot degree, use ASCII-code order (smaller one appears first). 13 | - If less than `3` hot sentences exist, return as many as you can. 14 | - When the input is a special character, it means the sentence ends, and in this case, you need to return an empty list. 15 | 16 | Implement the `AutocompleteSystem` class: 17 | 18 | - `AutocompleteSystem(String[] sentences, int[] times)` Initializes the object with the `sentences` and `times` arrays. 19 | - `List input(char c)` This indicates that the user typed the character `c` 20 | - Returns an empty array `[]` if `c == '#'` and stores the inputted sentence in the system. 21 | - Returns the top `3` historical hot sentences that have the same prefix as the part of the sentence already typed. If there are fewer than `3` matches, return them all. 22 | 23 | **Example 1:** 24 | 25 | ``` 26 | Input 27 | ["AutocompleteSystem", "input", "input", "input", "input"] 28 | [[["i love you", "island", "iroman", "i love leetcode"], [5, 3, 2, 2]], ["i"], [" "], ["a"], ["#"]] 29 | Output 30 | [null, ["i love you", "island", "i love leetcode"], ["i love you", "i love leetcode"], [], []] 31 | 32 | Explanation 33 | AutocompleteSystem obj = new AutocompleteSystem(["i love you", "island", "iroman", "i love leetcode"], [5, 3, 2, 2]); 34 | obj.input("i"); // return ["i love you", "island", "i love leetcode"]. There are four sentences that have prefix "i". Among them, "ironman" and "i love leetcode" have same hot degree. Since ' ' has ASCII code 32 and 'r' has ASCII code 114, "i love leetcode" should be in front of "ironman". Also we only need to output top 3 hot sentences, so "ironman" will be ignored. 35 | obj.input(" "); // return ["i love you", "i love leetcode"]. There are only two sentences that have prefix "i ". 36 | obj.input("a"); // return []. There are no sentences that have prefix "i a". 37 | obj.input("#"); // return []. The user finished the input, the sentence "i a" should be saved as a historical sentence in system. And the following input will be counted as a new search. 38 | ``` 39 | 40 | **Constraints:** 41 | 42 | - `n == sentences.length` 43 | - `n == times.length` 44 | - `1 <= n <= 100` 45 | - `1 <= sentences[i].length <= 100` 46 | - `1 <= times[i] <= 50` 47 | - `c` is a lowercase English letter, a hash `'#'`, or space `' '`. 48 | - Each tested sentence will be a sequence of characters `c` that end with the character `'#'`. 49 | - Each tested sentence will have a length in the range `[1, 200]`. 50 | - The words in each input sentence are separated by single spaces. 51 | - At most `5000` calls will be made to `input`. 52 | 53 | ### Standard Solution 54 | 55 | ```java 56 | class AutocompleteSystem{ 57 | class TrieNode implements Comparable { 58 | TrieNode[] children; // children array 59 | String s; // stored string 60 | int times; // times of call 61 | List hot; // top 3 hot strings 62 | 63 | public TrieNode(){ 64 | children = new TrieNode[128]; 65 | s = null; 66 | times = 0; 67 | hot = new ArrayList<>(); 68 | } 69 | 70 | public int compareTo(TrieNode o){ 71 | if (this.times == o.times){ 72 | // compare the string alphebet 73 | return this.s.compareTo(o.s); 74 | } 75 | return o.times - this.times; 76 | } 77 | 78 | public void update(TrieNode node){ 79 | if (!this.hot.contains(node)){ 80 | this.hot.add(node); 81 | } 82 | Collections.sort(hot); 83 | if (hot.size() > 3){ 84 | hot.remove(hot.size() - 1); 85 | } 86 | } 87 | } 88 | 89 | TrieNode root; 90 | TrieNode cur; 91 | StringBuilder sb; 92 | 93 | public AutocompleteSystem(String[] sentences, int[] times){ 94 | root = new TrieNode(); 95 | cur = root; 96 | sb = new StringBuilder(); 97 | 98 | for (int i = 0; i < times.length; i++){ 99 | add(sentences[i], times[i]); 100 | } 101 | } 102 | 103 | public void add(String sentence, int t){ 104 | TrieNode tmp = root; 105 | List visited = new ArrayList<>(); 106 | for (char c : sentence.toCharArray()){ 107 | if (tmp.children[c] == null){ 108 | tmp.children[c] = new TrieNode(); 109 | } 110 | tmp = tmp.children[c]; 111 | visited.add(tmp); 112 | } 113 | 114 | tmp.s = sentence; 115 | tmp.times += t; 116 | 117 | for (TrieNode node : visited){ 118 | node.update(tmp); 119 | } 120 | } 121 | 122 | public List input(char c){ 123 | List res = new ArrayList<>(); 124 | if (c == '#'){ 125 | add(sb.toString(), 1); 126 | sb = new StringBuilder(); 127 | cur = root; 128 | return res; 129 | } 130 | 131 | sb.append(c); 132 | if (cur != null){ 133 | cur = cur.children[c]; 134 | } 135 | 136 | if (cur == null) return res; 137 | for (TrieNode node : cur.hot){ 138 | res.add(node.s); 139 | } 140 | return res; 141 | } 142 | } 143 | ``` 144 | 145 | ## Design Add and Search Words Data Structure (Medium #211) 146 | 147 | **Question**: Design a data structure that supports adding new words and finding if a string matches any previously added string. 148 | 149 | Implement the `WordDictionary` class: 150 | 151 | - `WordDictionary()` Initializes the object. 152 | - `void addWord(word)` Adds `word` to the data structure, it can be matched later. 153 | - `bool search(word)` Returns `true` if there is any string in the data structure that matches `word` or `false` otherwise. `word` may contain dots `'.'` where dots can be matched with any letter. 154 | 155 | **Example:** 156 | 157 | ``` 158 | Input 159 | ["WordDictionary","addWord","addWord","addWord","search","search","search","search"] 160 | [[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]] 161 | Output 162 | [null,null,null,null,false,true,true,true] 163 | 164 | Explanation 165 | WordDictionary wordDictionary = new WordDictionary(); 166 | wordDictionary.addWord("bad"); 167 | wordDictionary.addWord("dad"); 168 | wordDictionary.addWord("mad"); 169 | wordDictionary.search("pad"); // return False 170 | wordDictionary.search("bad"); // return True 171 | wordDictionary.search(".ad"); // return True 172 | wordDictionary.search("b.."); // return True 173 | ``` 174 | 175 | **Constraints:** 176 | 177 | - `1 <= word.length <= 25` 178 | - `word` in `addWord` consists of lowercase English letters. 179 | - `word` in `search` consist of `'.'` or lowercase English letters. 180 | - There will be at most `3` dots in `word` for `search` queries. 181 | - At most `104` calls will be made to `addWord` and `search`. 182 | 183 | ### Standard Solution 184 | 185 | ```java 186 | class TrieNode { 187 | Map children = new HashMap(); 188 | boolean word = false; 189 | public TrieNode(){} 190 | } 191 | 192 | class WordDictionary { 193 | TrieNode trie; 194 | 195 | /** Initialize your data structure here.**/ 196 | public WordDictionary(){ 197 | trie = new TrieNode(); 198 | } 199 | 200 | /** Adds a word into the data structure **/ 201 | public void addWord(String word){ 202 | TrieNode node = trie; 203 | 204 | for (char ch : word.toCharArray()){ 205 | if (!node.children.containsKey(ch)){ 206 | node.children.put(ch, new TrieNode()); 207 | } 208 | node = node.children.get(ch); 209 | } 210 | node.word = true; 211 | } 212 | 213 | /** returns if the word is in the node.**/ 214 | public boolean searchInNode(String word, TrieNode node){ 215 | for (int i = 0; i < word.length(); i++){ 216 | char ch = word.charAt(i); 217 | if (!node.children.containsKey(ch)){ 218 | // if the current char is '.' 219 | if (ch == '.'){ 220 | for (char x : node.children.keySet()){ 221 | TrieNode child = node.children.get(x); 222 | if (searchInNode(word.substring(i + 1), child)){ 223 | return true; 224 | } 225 | } 226 | } 227 | // if no nodes lead to answer 228 | return false; 229 | } else { 230 | node = node.children.get(ch); 231 | } 232 | } 233 | return node.word; 234 | } 235 | 236 | public boolean search(String word){ 237 | return searchInNode(word, trie); 238 | } 239 | } 240 | ``` 241 | 242 | - Time complexity: $\mathcal{O}(M)$ for the "well-defined" words without dots, where M is the key length, and N is a number of keys, and $\mathcal{O}(N \cdot 26 ^ M)$ for the "undefined" words. That corresponds to the worst-case situation of searching an undefined word $\underbrace{.........}_\text{M times}$ which is one character longer than all inserted keys. 243 | - Space complexity: $\mathcal{O}(1)$ for the search of "well-defined" words without dots, and up to $\mathcal{O}(M)$ for the "undefined" words, to keep the recursion stack. 244 | 245 | ```java 246 | // another similar solution but easier to understand 247 | class WordDictionary { 248 | private WordDictionary[] children; 249 | boolean isEndOfWord; 250 | // Initialize your data structure here. 251 | public WordDictionary() { 252 | children = new WordDictionary[26]; 253 | isEndOfWord = false; 254 | } 255 | 256 | // Adds a word into the data structure. 257 | public void addWord(String word) { 258 | WordDictionary curr = this; 259 | for(char c: word.toCharArray()){ 260 | if(curr.children[c - 'a'] == null) 261 | curr.children[c - 'a'] = new WordDictionary(); 262 | curr = curr.children[c - 'a']; 263 | } 264 | curr.isEndOfWord = true; 265 | } 266 | 267 | // Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. 268 | public boolean search(String word) { 269 | WordDictionary curr = this; 270 | for(int i = 0; i < word.length(); ++i){ 271 | char c = word.charAt(i); 272 | if(c == '.'){ 273 | for(WordDictionary ch: curr.children) 274 | if(ch != null && ch.search(word.substring(i+1))) return true; 275 | return false; 276 | } 277 | if(curr.children[c - 'a'] == null) return false; 278 | curr = curr.children[c - 'a']; 279 | } 280 | return curr != null && curr.isEndOfWord; 281 | } 282 | } 283 | ``` 284 | 285 | -------------------------------------------------------------------------------- /System Design/System Design Tutorial.md: -------------------------------------------------------------------------------- 1 | # System Design Tutorial 2 | 3 | This course is divided into three main chapters - 4 | 5 | - **Basics**: This chapter is specially designed for novices to build a strong foundation of System Design fundamentals that are useful in understanding the dynamics of highly scalable systems. 6 | - **Case Studies**: This chapter aims to model the System Design of some of the most frequently asked interview questions, like designing an e-commerce app. 7 | - **Problem Solving**: This chapter lets you apply your understanding of the preceding chapters with the help of some practice problems and quizzes. 8 | 9 | ### **High-Level Design** 10 | 11 | #### Structure 12 | 13 | * Overall architecture 14 | * How can the system be built such that it takes care of scalability, latency, and other performance requirements 15 | * Systems and Services 16 | * In this part of high-level design, we need to decide how the system will be broken down, what the microservices will be, and their scope. 17 | * Interaction between Systems 18 | * How will the systems interact with each other? 19 | * What protocols will you use? 20 | * Will it be synchronous or asynchronous? 21 | * You need to make these decisions based on the requirements 22 | * Database(Design database schema) 23 | * What databases will you need? 24 | * What kind of data do you need to store? 25 | * SQL or NoSQL, what will be the database schema? 26 | * Examples 27 | 28 | #### Expectation 29 | 30 | * Requirements: fulfill requirements, highly scalable 31 | * Futuristic: open to future improvement, non-restrictive to specific items, modular 32 | 33 | img 34 | 35 | #### Approach 36 | 37 | * Functional requirements 38 | * Features 39 | * User Journeys 40 | * Non-functional requirements 41 | * Scale 42 | * Latency 43 | * Consistency 44 | 45 | #### Approach Solution 46 | 47 | img 48 | 49 | * Propose a design 50 | * This means breaking down the system into multiple components and identifying which microservices are needed, setting up interactions between these microservices, finalizing databases, etc. 51 | * Talk about trade-offs 52 | * What database would be more suitable? Do you need SQL or NoSQL? You keep adjusting your design based on your **design decisions** and tradeoffs until the requirements have been satisfactorily met. 53 | * Discuss design choices 54 | * Limitations of the design 55 | * If your system does not have a monitoring component, it would be good to mention this at this stage. 56 | 57 | ### Low-Level Design 58 | 59 | image-20220821113249954 60 | 61 | #### Structure 62 | 63 | * Dive deeper into the implemention of the individual system 64 | * Covers a sub-system 65 | * Optimisations within the sub-system 66 | * Sample: Design a parking lot, Uber's pricing system 67 | 68 | #### Expection 69 | 70 | * Requirements: 71 | * fulfulls function and non-function requirement, good code structure, working + clean code 72 | * Modular, futuristic, non-restrictive 73 | * Code: 74 | * Interfaces 75 | * Class diagrams 76 | * Entities 77 | * Data model 78 | * Design patterns 79 | * Quality: 80 | * Working code, pass basic tests 81 | * Modular: able to add new features, code consistent when adding new features(old codes works well) 82 | * Testable: unit test cases 83 | * Expected Outcome (Example of Uber's pricing engine) 84 | * `getFareEstimate()` which fetches the `basePrice` and `surgePrice` and accordingly returns the total estimated fare.`getBasePrice()` will return the base price based on distance, time, and ride type. If there are too many requests from users for the current number of drivers, we will apply a surge price and increase the fare. 85 | * We could use the Factory Design Pattern here and add two factories, `BasePriceCalculatorFactory` for calculating `basePrice` and `SurgePriceCalculatorFactory` for calculating `surgePrice`. 86 | 87 | ![image-20220821113020030](/Users/wenjunhan/Library/Application Support/typora-user-images/image-20220821113020030.png) 88 | 89 | #### How to Approach LLD Interview 90 | 91 | * Lock down the requirements, both functional and non-functional. The idea is to limit the scope early on so we don't get lost trying to build too many things. 92 | * The next step is the code. The first thing to do here will be to define the interfaces. 93 | * Next are class diagrams, where we define what classes we need, how they will interact, what hierarchy they will follow, etc. 94 | * We will further define our entities and data models, and while doing all this, we need to consider what design pattern will be most suitable. 95 | * Talking about design patterns, the next step would be to ensure code quality. This means we should have a working code with some basic test cases that are passing for the logic we have written. -------------------------------------------------------------------------------- /Trie/Trie Tutorial.md: -------------------------------------------------------------------------------- 1 | # Trie Tutorial 2 | 3 | ## Definition 4 | 5 | A `Trie` is a special form of a `Nary tree`. Typically, a trie is used to `store strings`. Each Trie node represents `a string` (`a prefix`). Each node might have several children nodes while the paths to different children nodes represent different characters. And the strings the child nodes represent will be the `origin string` represented by the node itself plus `the character on the path`. 6 | 7 | img 8 | 9 | ### Array Solution 10 | 11 | ```java 12 | class TrieNode { 13 | // change this value to adapt to different cases 14 | public static final N = 26; 15 | public TrieNode[] children = new TrieNode[N]; 16 | 17 | // you might need some extra values according to different cases 18 | }; 19 | ``` 20 | 21 | ### Map Solution 22 | 23 | ```java 24 | class TrieNode { 25 | public Map children = new HashMap<>(); 26 | 27 | // you might need some extra values according to different cases 28 | }; 29 | ``` 30 | 31 | ## Data Structure - Implement Trie(Prefix Tree)(Medium #208) 32 | 33 | ```java 34 | class Trie { 35 | 36 | private TrieNode root; 37 | 38 | public Trie() { 39 | root = new TrieNode(); 40 | } 41 | 42 | // insert a word into trie 43 | // Time complexity : O(m), where m is the key length. 44 | // Space complexity : O(m). 45 | public void insert(String word) { 46 | TrieNode node = root; 47 | for (int i = 0; i < word.length(); i++){ 48 | char currentChar = word.charAt(i); 49 | if (!node.containsKey(currentChar)){ 50 | node.put(currentChar, new TrieNode()); 51 | } 52 | node = node.get(currentChar); 53 | } 54 | node.setEnd(); 55 | } 56 | 57 | 58 | // search prefix can be applied in search, but need to check isEnd 59 | // Time complexity : O(m) In each step of the algorithm we search for the next key character. 60 | // Space complexity : O(1) 61 | public boolean search(String word) { 62 | TrieNode node = searchPrefix(word); 63 | return node != null && node.isEnd(); 64 | } 65 | 66 | private TrieNode searchPrefix(String word){ 67 | TrieNode node = root; 68 | for (int i = 0; i < word.length(); i++){ 69 | char currLetter = word.charAt(i); 70 | if (node.containsKey(currLetter)){ 71 | node = node.get(currLetter); 72 | } 73 | else { 74 | return null; 75 | } 76 | } 77 | return node; 78 | } 79 | 80 | // apply the previous method 81 | // Time complexity : O(m) 82 | // Space complexity : O(1) 83 | public boolean startsWith(String prefix) { 84 | TrieNode node = searchPrefix(prefix); 85 | return node != null; 86 | } 87 | } 88 | 89 | class TrieNode { 90 | // R links to node children 91 | private TrieNode[] links; 92 | private final int R = 26; 93 | private boolean isEnd; 94 | 95 | public TrieNode(){ 96 | links = new TrieNode[R]; 97 | } 98 | 99 | public boolean containsKey(char ch){ 100 | return links[ch - 'a'] != null; 101 | } 102 | 103 | public TrieNode get(char ch){ 104 | return links[ch - 'a']; 105 | } 106 | 107 | public void put(char ch, TrieNode node){ 108 | links[ch - 'a'] = node; 109 | } 110 | 111 | public void setEnd(){ 112 | isEnd = true; 113 | } 114 | 115 | public boolean isEnd(){ 116 | return isEnd; 117 | } 118 | } 119 | 120 | /** 121 | * Your Trie object will be instantiated and called as such: 122 | * Trie obj = new Trie(); 123 | * obj.insert(word); 124 | * boolean param_2 = obj.search(word); 125 | * boolean param_3 = obj.startsWith(prefix); 126 | */ 127 | ``` 128 | 129 | ```java 130 | // map implementation 131 | class Trie { 132 | class TrieNode { 133 | public boolean isWord; 134 | public Map childrenMap = new HashMap<>(); 135 | } 136 | 137 | private TrieNode root; 138 | 139 | /** Initialize your data structure here. */ 140 | public Trie() { 141 | root = new TrieNode(); 142 | } 143 | 144 | /** Inserts a word into the trie. */ 145 | public void insert(String word) { 146 | TrieNode cur = root; 147 | for(int i = 0; i < word.length(); i++){ 148 | char c = word.charAt(i); 149 | if(cur.childrenMap.get(c) == null){ 150 | // insert a new node if the path does not exist 151 | cur.childrenMap.put(c, new TrieNode()); 152 | } 153 | cur = cur.childrenMap.get(c); 154 | } 155 | cur.isWord = true; 156 | } 157 | 158 | /** Returns if the word is in the trie. */ 159 | public boolean search(String word) { 160 | TrieNode cur = root; 161 | for(int i = 0; i < word.length(); i++) { 162 | char c = word.charAt(i); 163 | if(cur.childrenMap.get(c) == null) { 164 | return false; 165 | } 166 | cur = cur.childrenMap.get(c); 167 | } 168 | return cur.isWord; 169 | } 170 | 171 | /** Returns if there is any word in the trie that starts with the given prefix. */ 172 | public boolean startsWith(String prefix) { 173 | TrieNode cur = root; 174 | for(int i = 0;i < prefix.length(); i++){ 175 | char c = prefix.charAt(i); 176 | if(cur.childrenMap.get(c) == null) { 177 | return false; 178 | } 179 | cur = cur.childrenMap.get(c); 180 | } 181 | return true; 182 | } 183 | } 184 | ``` 185 | 186 | * The time complexity to search in Trie is `O(M)`. 187 | * The space complexity of hash table is `O(M * N)`. 188 | 189 | ## Map Sum Pairs (Medium #677) 190 | 191 | **Question**: Design a map that allows you to do the following: 192 | 193 | - Maps a string key to a given value. 194 | - Returns the sum of the values that have a key with a prefix equal to a given string. 195 | 196 | Implement the `MapSum` class: 197 | 198 | - `MapSum()` Initializes the `MapSum` object. 199 | - `void insert(String key, int val)` Inserts the `key-val` pair into the map. If the `key` already existed, the original `key-value` pair will be overridden to the new one. 200 | - `int sum(string prefix)` Returns the sum of all the pairs' value whose `key` starts with the `prefix`. 201 | 202 | **Example 1:** 203 | 204 | ``` 205 | Input 206 | ["MapSum", "insert", "sum", "insert", "sum"] 207 | [[], ["apple", 3], ["ap"], ["app", 2], ["ap"]] 208 | Output 209 | [null, null, 3, null, 5] 210 | 211 | Explanation 212 | MapSum mapSum = new MapSum(); 213 | mapSum.insert("apple", 3); 214 | mapSum.sum("ap"); // return 3 (apple = 3) 215 | mapSum.insert("app", 2); 216 | mapSum.sum("ap"); // return 5 (apple + app = 3 + 2 = 5) 217 | ``` 218 | 219 | **Constraints:** 220 | 221 | - `1 <= key.length, prefix.length <= 50` 222 | - `key` and `prefix` consist of only lowercase English letters. 223 | - `1 <= val <= 1000` 224 | - At most `50` calls will be made to `insert` and `sum`. 225 | 226 | ### Standard Solution 227 | 228 | #### Solution #1 Trie 229 | 230 | ```java 231 | class TrieNode { 232 | public Map children; 233 | public int score; 234 | 235 | public TrieNode() { 236 | children = new HashMap<>(); 237 | score = 0; 238 | } 239 | } 240 | 241 | class MapSum { 242 | // trie data structure 243 | public TrieNode root; 244 | public Map map; 245 | 246 | public MapSum() { 247 | root = new TrieNode(); 248 | map = new HashMap<>(); 249 | } 250 | 251 | public void insert(String key, int val) { 252 | int delta = val - map.getOrDefault(key, 0); 253 | map.put(key, val); 254 | TrieNode curr = root; 255 | curr.score += delta; 256 | for (int i = 0; i < key.length(); i++){ 257 | char currLetter = key.charAt(i); 258 | curr.children.putIfAbsent(currLetter, new TrieNode()); 259 | curr = curr.children.get(currLetter); 260 | curr.score += delta; 261 | } 262 | } 263 | 264 | public int sum(String prefix) { 265 | TrieNode curr = root; 266 | for (char c : prefix.toCharArray()){ 267 | curr = curr.children.get(c); 268 | if (curr == null) return 0; 269 | } 270 | return curr.score; 271 | } 272 | } 273 | ``` 274 | 275 | - Time Complexity: Every inserts operation is $O(K)$, where K is the length of the key. Every sum operation is $O(K)$ 276 | - Space Complexity: The space used is linear in the size of the total input. 277 | 278 | #### Solution #2 Prefix Hashmap 279 | 280 | ```java 281 | Map map; 282 | Map score; 283 | public MapSum() { 284 | map = new HashMap(); 285 | score = new HashMap(); 286 | } 287 | public void insert(String key, int val) { 288 | int delta = val - map.getOrDefault(key, 0); 289 | map.put(key, val); 290 | String prefix = ""; 291 | for (char c: key.toCharArray()) { 292 | prefix += c; 293 | score.put(prefix, score.getOrDefault(prefix, 0) + delta); 294 | } 295 | } 296 | public int sum(String prefix) { 297 | return score.getOrDefault(prefix, 0); 298 | } 299 | ``` 300 | 301 | - Time Complexity: Every insert operation is $O(K^2)$, where K is the length of the key, as K strings are made of an average length of K. Every sum operation is $O(1)$ 302 | - Space Complexity: The space used by `map` and `score` is linear in the size of all input `key` and `val` values combined. 303 | 304 | ## Replace Words (Medium #648) 305 | 306 | **Question**: In English, we have a concept called **root**, which can be followed by some other word to form another longer word - let's call this word **successor**. For example, when the **root** `"an"` is followed by the **successor** word `"other"`, we can form a new word `"another"`. 307 | 308 | Given a `dictionary` consisting of many **roots** and a `sentence` consisting of words separated by spaces, replace all the **successors** in the sentence with the **root** forming it. If a **successor** can be replaced by more than one **root**, replace it with the **root** that has **the shortest length**. 309 | 310 | Return *the `sentence`* after the replacement. 311 | 312 | **Example 1:** 313 | 314 | ``` 315 | Input: dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery" 316 | Output: "the cat was rat by the bat" 317 | ``` 318 | 319 | **Example 2:** 320 | 321 | ``` 322 | Input: dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs" 323 | Output: "a a b c" 324 | ``` 325 | 326 | **Constraints:** 327 | 328 | - `1 <= dictionary.length <= 1000` 329 | - `1 <= dictionary[i].length <= 100` 330 | - `dictionary[i]` consists of only lower-case letters. 331 | - `1 <= sentence.length <= 106` 332 | - `sentence` consists of only lower-case letters and spaces. 333 | - The number of words in `sentence` is in the range `[1, 1000]` 334 | - The length of each word in `sentence` is in the range `[1, 1000]` 335 | - Every two consecutive words in `sentence` will be separated by exactly one space. 336 | - `sentence` does not have leading or trailing spaces. 337 | 338 | ### Standard Solution 339 | 340 | #### Solution #1 Trie 341 | 342 | ```java 343 | class TrieNode { 344 | TrieNode[] children; 345 | String word; 346 | TrieNode(){ 347 | children = new TrieNode[26]; 348 | } 349 | } 350 | class Solution { 351 | public String replaceWords(List dictionary, String sentence) { 352 | // use a trie structure to store prefix to trie 353 | TrieNode trie = new TrieNode(); 354 | for (String prefix : dictionary){ 355 | TrieNode curr = trie; 356 | for (char letter : prefix.toCharArray()){ 357 | // create a children trienode 358 | if (curr.children[letter - 'a'] == null){ 359 | curr.children[letter - 'a'] = new TrieNode(); 360 | } 361 | curr = curr.children[letter - 'a']; 362 | } 363 | curr.word = prefix; 364 | } 365 | 366 | // construct a new string to return 367 | StringBuilder res = new StringBuilder(); 368 | 369 | for (String word : sentence.split("\\s+")){ 370 | if (res.length() > 0) res.append(" "); 371 | 372 | TrieNode curr = trie; 373 | for (char letter : word.toCharArray()){ 374 | if (curr.children[letter - 'a'] == null || curr.word != null){ 375 | break; 376 | } 377 | curr = curr.children[letter - 'a']; 378 | } 379 | res.append(curr.word != null ? curr.word : word); 380 | } 381 | return res.toString(); 382 | } 383 | } 384 | ``` 385 | 386 | - Time Complexity: $O(N)$ where N is the length of the `sentence`. Every query of a word is in linear time. 387 | - Space Complexity: $O(N)$ the size of our trie. -------------------------------------------------------------------------------- /Two Pointers/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhwenjun/leetcode-note-java/9aa900c1acedf4515f351359a5834eb38018e426/Two Pointers/.DS_Store -------------------------------------------------------------------------------- /Two Pointers/Two Pointers Problems Part #1.md: -------------------------------------------------------------------------------- 1 | # Two Pointers Problems Part #1 2 | 3 | ## Maximize Distance to Closest Person(Medium #849) 4 | 5 | **Question**: You are given an array representing a row of `seats` where `seats[i] = 1` represents a person sitting in the `ith` seat, and `seats[i] = 0` represents that the `ith` seat is empty **(0-indexed)**. 6 | 7 | There is at least one empty seat, and at least one person sitting. 8 | 9 | Alex wants to sit in the seat such that the distance between him and the closest person to him is maximized. 10 | 11 | Return *that maximum distance to the closest person*. 12 | 13 | **Example 1:** 14 | 15 | img 16 | 17 | ``` 18 | Input: seats = [1,0,0,0,1,0,1] 19 | Output: 2 20 | Explanation: 21 | If Alex sits in the second open seat (i.e. seats[2]), then the closest person has distance 2. 22 | If Alex sits in any other open seat, the closest person has distance 1. 23 | Thus, the maximum distance to the closest person is 2. 24 | ``` 25 | 26 | **Example 2:** 27 | 28 | ``` 29 | Input: seats = [1,0,0,0] 30 | Output: 3 31 | Explanation: 32 | If Alex sits in the last seat (i.e. seats[3]), the closest person is 3 seats away. 33 | This is the maximum distance possible, so the answer is 3. 34 | ``` 35 | 36 | **Example 3:** 37 | 38 | ``` 39 | Input: seats = [0,1] 40 | Output: 1 41 | ``` 42 | 43 | **Constraints:** 44 | 45 | - `2 <= seats.length <= 2 * 104` 46 | - `seats[i]` is `0` or `1`. 47 | - At least one seat is **empty**. 48 | - At least one seat is **occupied**. 49 | 50 | ### My Solution 51 | 52 | ```java 53 | public int maxDistToClosest(int[] seats) { 54 | int maxDist = 0, low, high; 55 | 56 | for (low = 0, high = 0; high < seats.length; ++high){ 57 | if (seats[high] == 1){ 58 | int totalDist = high - low; 59 | if (seats[low] == 1){ 60 | maxDist = Math.max((int)Math.ceil(totalDist/2), maxDist); 61 | } 62 | else { 63 | maxDist = Math.max(totalDist, maxDist); 64 | } 65 | low = high; 66 | } 67 | } 68 | if (low == seats.length - 1) return Math.max(maxDist, high - low); 69 | return Math.max(maxDist, high - low - 1); 70 | } 71 | ``` 72 | 73 | ### Standard Solution 74 | 75 | #### Solution #1 Next Array 76 | 77 | * Let `left[i]` be the distance from seat `i` to the closest person sitting to the left of `i`. Similarly, let `right[i]` be the distance to the closest person sitting to the right of `i`. This is motivated by the idea that the closest person in seat `i` sits a distance `min(left[i], right[i])` away. 78 | 79 | * **Algorithm** 80 | 81 | To construct `left[i]`, notice it is either `left[i-1] + 1` if the seat is empty, or `0` if it is full. `right[i]` is constructed in a similar way. 82 | 83 | ```java 84 | public int maxDistToClosest(int[] seats){ 85 | int N = seats.length; 86 | int[] left = new int[N], right = new int[N]; 87 | Arrays.fill(left, N); 88 | Arrays.fill(right, N); 89 | 90 | for (int i = 0; i < N; i++){ 91 | if (seats[i] == 1) left[i] = 0; //distance to the cloest one would be 0 92 | else if (i > 0) left[i] = left[i - 1] + 1; //add 1 distance comparing to the previous one 93 | } 94 | for (int i = N - 1; i >= 0; i--){ 95 | if (seats[i] == 1) right[i] = 0; //distance to the cloest one would be 0 96 | else if (i < N - 1) right[i] = right[i + 1] + 1; //add 1 distance comparing to the previous one 97 | } 98 | 99 | int ans = 0; 100 | for (int i = 0; i < N; i++){ 101 | if (seats[i] == 0){ 102 | ans = Math.max(ans, Math.min(left[i], right[i])); 103 | } 104 | } 105 | return ans; 106 | } 107 | ``` 108 | 109 | * Time complexity: $O(N)$ where N is the length of seats 110 | * Space complexity: $O(N)$ where the space used by left and right 111 | 112 | #### Solution #2 Two Pointer 113 | 114 | * **Algorithm**: 115 | * Keep track of `prev`, the filled seat at or to the left of `i`, and `future`, the filled seat at or to the right of `i`. 116 | * Then at seat `i`, the closest person is `min(i - prev, future - i)`, with one exception. `i - prev` should be considered infinite if there is no person to the left of seat `i`, and similarly `future - i` is infinite if there is no one to the right of seat `i`. 117 | 118 | ```java 119 | public int maxDistToClosest(int[] seats){ 120 | int N = seats.length; 121 | int prev = -1, future = 0; 122 | int ans = 0; 123 | 124 | for (int i = 0; i < N; i++){ 125 | if (seats[i] == 1){ 126 | prev = i; 127 | } else { 128 | while (future < N && seats[future] == 0 || future < i){ 129 | future++; 130 | } 131 | int left = prev == -1 ? N : i - prev; 132 | int right = future == N ? N : future - i; 133 | ans = Math.max(ans, Math.min(left, right)); 134 | } 135 | } 136 | return ans; 137 | } 138 | ``` 139 | 140 | * Time Complexity: $O(N)$, where $N$ is the length of `seats`. 141 | * Space Complexity: $O(1)$ 142 | 143 | #### Solution #3 Group by Zero 144 | 145 | * My solution is a combination of solution 2 and 3 146 | * In a group of `K` adjacent empty seats between two people, the answer is `(K+1) / 2`. 147 | * For groups of empty seats between the edge of the row and one other person, the answer is `K`, and we should take into account those answers too. 148 | 149 | ```java 150 | public int maxDistToClosest(int[] seats){ 151 | int N = seats.length; 152 | int K = 0;//current longest group of empty seats 153 | int ans = 0; 154 | 155 | for (int i = 0; i < N; i++){ 156 | if (seats[i] == 1){ 157 | K = 0; 158 | } else { 159 | K++; 160 | ans = Math.max(ans, (K + 1) / 2); 161 | } 162 | } 163 | 164 | for (int i = 0; i < N; i++){// in case starts from 0 165 | if (seats[i] == 1){ 166 | ans = Math.max(ans, i); 167 | break; 168 | } 169 | } 170 | 171 | for (int i = N - 1; i >= 0; i--){// in case ends with 0 172 | if (seats[i] == 1){ 173 | ans = Math.max(ans, N - 1 - i); 174 | break; 175 | } 176 | } 177 | return ans; 178 | } 179 | ``` 180 | 181 | * Time Complexity: $O(N)$, where N is the length of `seats`. 182 | * Space Complexity: $O(1)$. 183 | 184 | ## 3Sum (Medium #15) 185 | 186 | **Question**: Given an integer array nums, return all the triplets `[nums[i], nums[j], nums[k]]` such that `i != j`, `i != k`, and `j != k`, and `nums[i] + nums[j] + nums[k] == 0`. 187 | 188 | Notice that the solution set must not contain duplicate triplets. 189 | 190 | **Example 1:** 191 | 192 | ``` 193 | Input: nums = [-1,0,1,2,-1,-4] 194 | Output: [[-1,-1,2],[-1,0,1]] 195 | Explanation: 196 | nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. 197 | nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. 198 | nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. 199 | The distinct triplets are [-1,0,1] and [-1,-1,2]. 200 | Notice that the order of the output and the order of the triplets does not matter. 201 | ``` 202 | 203 | **Example 2:** 204 | 205 | ``` 206 | Input: nums = [0,1,1] 207 | Output: [] 208 | Explanation: The only possible triplet does not sum up to 0. 209 | ``` 210 | 211 | **Example 3:** 212 | 213 | ``` 214 | Input: nums = [0,0,0] 215 | Output: [[0,0,0]] 216 | Explanation: The only possible triplet sums up to 0. 217 | ``` 218 | 219 | **Constraints:** 220 | 221 | - `3 <= nums.length <= 3000` 222 | - `-105 <= nums[i] <= 105` 223 | 224 | ### My Solution 225 | 226 | ```java 227 | // two pointers 228 | // 1. sum to 0: location < 0 229 | // 2. low and high pointer 230 | List> res; 231 | int[] nums; 232 | 233 | public List> threeSum(int[] nums) { 234 | Arrays.sort(nums); 235 | res = new ArrayList<>(); 236 | this.nums = nums; 237 | for (int i = 0; i < nums.length && nums[i] <= 0; i++){ 238 | // in case duplicate situations 239 | if (i == 0 || nums[i - 1] != nums[i]){ 240 | findSolution(i); 241 | } 242 | } 243 | return res; 244 | } 245 | 246 | public void findSolution(int loc){ 247 | 248 | int low = loc + 1, high = nums.length - 1; 249 | while(low < high){ 250 | int sum = nums[low] + nums[high] + nums[loc]; 251 | if (sum == 0){ 252 | res.add(Arrays.asList(nums[loc], nums[low++], nums[high--])); 253 | while(low < high && nums[low] == nums[low - 1]){ 254 | low++; 255 | } 256 | } 257 | else if (sum < 0){ 258 | low++; 259 | } 260 | else { 261 | high--; 262 | } 263 | } 264 | } 265 | ``` 266 | 267 | ### Standard Solution 268 | 269 | #### Solution #1 Two pointer 270 | 271 | * Same as my solution 272 | 273 | ```java 274 | public List> threeSum(int[] nums) { 275 | Arrays.sort(nums); 276 | List> res = new ArrayList<>(); 277 | for (int i = 0; i < nums.length && nums[i] <= 0; ++i) 278 | // skip the duplicate number 279 | if (i == 0 || nums[i - 1] != nums[i]) { 280 | twoSumII(nums, i, res); 281 | } 282 | return res; 283 | } 284 | void twoSumII(int[] nums, int i, List> res) { 285 | int lo = i + 1, hi = nums.length - 1; 286 | while (lo < hi) { 287 | int sum = nums[i] + nums[lo] + nums[hi]; 288 | if (sum < 0) { 289 | ++lo; 290 | } else if (sum > 0) { 291 | --hi; 292 | } else { 293 | // continue to move low and high pointer to next possible solution 294 | res.add(Arrays.asList(nums[i], nums[lo++], nums[hi--])); 295 | // skip the duplicate number 296 | while (lo < hi && nums[lo] == nums[lo - 1]) 297 | ++lo; 298 | } 299 | } 300 | } 301 | ``` 302 | 303 | - Time Complexity: $\mathcal{O}(n^2)$. `twoSumII` is $\mathcal{O}(n)$, and we call it n times. 304 | 305 | Sorting the array takes $\mathcal{O}(n\log{n})$, so overall complexity is $\mathcal{O}(n\log{n} + n^2)$. This is asymptotically equivalent to $\mathcal{O}(n^2)$ 306 | 307 | - Space Complexity: from $\mathcal{O}(\log{n})$ to $\mathcal{O}(n)$, depending on the implementation of the sorting algorithm. For the purpose of complexity analysis, we ignore the memory required for the output. 308 | 309 | #### Solution #2 HashSet 310 | 311 | ```java 312 | public List> threeSum(int[] nums) { 313 | Arrays.sort(nums); 314 | List> res = new ArrayList<>(); 315 | for (int i = 0; i < nums.length && nums[i] <= 0; ++i) 316 | if (i == 0 || nums[i - 1] != nums[i]) { 317 | twoSum(nums, i, res); 318 | } 319 | return res; 320 | } 321 | void twoSum(int[] nums, int i, List> res) { 322 | var seen = new HashSet(); 323 | for (int j = i + 1; j < nums.length; ++j) { 324 | int complement = -nums[i] - nums[j]; 325 | if (seen.contains(complement)) { 326 | res.add(Arrays.asList(nums[i], nums[j], complement)); 327 | while (j + 1 < nums.length && nums[j] == nums[j + 1]) 328 | ++j; 329 | } 330 | seen.add(nums[j]); 331 | } 332 | } 333 | ``` 334 | 335 | - Time Complexity: $\mathcal{O}(n^2)$. `twoSum` is $\mathcal{O}(n)$, and we call it n times. 336 | 337 | Sorting the array takes $\mathcal{O}(n\log{n})$, so overall complexity is $\mathcal{O}(n\log{n} + n^2)$. This is asymptotically equivalent to $\mathcal{O}(n^2)$ 338 | 339 | - Space Complexity: $\mathcal{O}(n)$ for the hashset. 340 | 341 | ## Trapping Rain Water (Hard #42) 342 | 343 | **Question**: Given `n` non-negative integers representing an elevation map where the width of each bar is `1`, compute how much water it can trap after raining. 344 | 345 | **Example 1:** 346 | 347 | ![img](https://assets.leetcode.com/uploads/2018/10/22/rainwatertrap.png) 348 | 349 | ``` 350 | Input: height = [0,1,0,2,1,0,1,3,2,1,2,1] 351 | Output: 6 352 | Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. 353 | ``` 354 | 355 | **Example 2:** 356 | 357 | ``` 358 | Input: height = [4,2,0,3,2,5] 359 | Output: 9 360 | ``` 361 | 362 | **Constraints:** 363 | 364 | - `n == height.length` 365 | - `1 <= n <= 2 * 104` 366 | - `0 <= height[i] <= 105` 367 | 368 | ### Standard Solution 369 | 370 | #### Solution #1 Two Pointers 371 | 372 | * Use two pointers: left pointer and the right pointer. 373 | * Track the left maximum and right maximum. 374 | * Always keep the larger pointer, and move the lower pointer, in this way, we can calcuate the trapping rain water by `max - curr` 375 | 376 | ```java 377 | public int trap(int[] height) { 378 | // two pointers: low, high 379 | int left = 0, right = height.length - 1; 380 | int maxLeft = 0, maxRight = 0; 381 | int water = 0; 382 | 383 | while(left < right){ 384 | // current right pointer is max right 385 | // 1. compare two pointer, always keep the high one 386 | if (height[left] < height[right]){ 387 | if (height[left] >= maxLeft){ 388 | maxLeft = height[left]; 389 | } 390 | else water += maxLeft - height[left]; 391 | // 2. move the lower pointer, record left and right max 392 | left++; 393 | } 394 | else { 395 | // 3. when move the lower one, record max - low value 396 | if (height[right] >= maxRight){ 397 | maxRight = height[right]; 398 | } 399 | else water += maxRight - height[right]; 400 | right--; 401 | } 402 | } 403 | return water; 404 | } 405 | ``` 406 | 407 | - Time complexity: $O(n)$. Single iteration of $O(n)$. 408 | - Space complexity: $O(1)$ extra space. Only constant space required for $\text{left}$, $\text{right}$, $\text{left\_max}$ and $\text{right\_max}$. 409 | 410 | ## Product of Array Except Self (Medium #238) 411 | 412 | **Question**: Given an integer array `nums`, return *an array* `answer` *such that* `answer[i]` *is equal to the product of all the elements of* `nums` *except* `nums[i]`. 413 | 414 | The product of any prefix or suffix of `nums` is **guaranteed** to fit in a **32-bit** integer. 415 | 416 | You must write an algorithm that runs in `O(n)` time and without using the division operation. 417 | 418 | **Example 1:** 419 | 420 | ``` 421 | Input: nums = [1,2,3,4] 422 | Output: [24,12,8,6] 423 | ``` 424 | 425 | **Example 2:** 426 | 427 | ``` 428 | Input: nums = [-1,1,0,-3,3] 429 | Output: [0,0,9,0,0] 430 | ``` 431 | 432 | **Constraints:** 433 | 434 | - `2 <= nums.length <= 105` 435 | - `-30 <= nums[i] <= 30` 436 | - The product of any prefix or suffix of `nums` is **guaranteed** to fit in a **32-bit** integer. 437 | 438 | **Follow up:** Can you solve the problem in `O(1) `extra space complexity? (The output array **does not** count as extra space for space complexity analysis.) 439 | 440 | ### My Solution 441 | 442 | ```java 443 | // correct but time limit exceeded 444 | public int[] productExceptSelf(int[] nums) { 445 | // product every digit in the array, then change i element back to origin 446 | int[] res = new int[nums.length]; 447 | Arrays.fill(res, 1); 448 | for (int i = 0; i < nums.length; i++){ 449 | int curr = nums[i]; 450 | for (int j = 0; j < nums.length; j++){ 451 | if (i == j) continue; 452 | res[j] = res[j] * curr; 453 | } 454 | } 455 | return res; 456 | } 457 | ``` 458 | 459 | ### Standard Solution 460 | 461 | #### Solution #1 Left and Right Product Lists 462 | 463 | ![img](https://leetcode.com/problems/product-of-array-except-self/Figures/238/diag-1.png) 464 | 465 | * Use previous product times `nums[i - 1]` to avoid times themselves. Similarly, calculate the product starting from the right direction. 466 | 467 | ```java 468 | public int[] productExceptSelf(int[] nums){ 469 | // the length of the input array 470 | int length = nums.length; 471 | 472 | // the left and right arrays as described in the algorithm 473 | int[] L = new int[length]; 474 | int[] R = new int[length]; 475 | 476 | // final answer array to be returned 477 | int[] answer = new int[length]; 478 | 479 | // L[i] contains the product of all elements to the left 480 | // note: for the element at index '0', no elements to the left, L[0] would be 1 481 | L[0] = 1; 482 | for (int i = 1; i < length; i++){ 483 | // L[i - 1] already contains the product of elements to the left of 'i - 1' 484 | L[i] = nums[i - 1] * L[i - 1]; 485 | } 486 | 487 | R[length - 1] = 1; 488 | for (int i = length - 2; i >= 0; i--){ 489 | // R[i + 1] already contains the product of elements to the right of 'i + 1' 490 | R[i] = nums[i + 1] * R[i + 1]; 491 | } 492 | 493 | // constructing the answer array 494 | for (int i = 0; i < length; i++){ 495 | // For the first element, R[i] would be product except for self 496 | // For the last element of the array, product except for self would be L[i] 497 | // Else, multiple product of all elements to the left and to the right 498 | answer[i] = L[i] * R[i]; 499 | } 500 | return answer; 501 | } 502 | ``` 503 | 504 | - Time complexity: $O(N)$ where N represents the number of elements in the input array. We use one iteration to construct the array L, one to construct the array R, and one last to construct the `answer` array using L and R. 505 | - Space complexity: $O(N)$ used up by the two intermediate arrays that we constructed to keep track of the product of elements to the left and right. 506 | 507 | #### Solution #2 O(1) Space Approach 508 | 509 | * Similar idea to the first solution, but more space efficient 510 | 511 | ```java 512 | public int[] productExceptSelf(int[] nums) { 513 | // The length of the input array 514 | int length = nums.length; 515 | 516 | // Final answer array to be returned 517 | int[] answer = new int[length]; 518 | 519 | answer[0] = 1; 520 | for (int i = 1; i < length; i++) { 521 | answer[i] = nums[i - 1] * answer[i - 1]; 522 | } 523 | 524 | int R = 1; 525 | for (int i = length - 1; i >= 0; i--) { 526 | 527 | // For the index 'i', R would contain the 528 | // product of all elements to the right. We update R accordingly 529 | answer[i] = answer[i] * R; 530 | R *= nums[i]; 531 | } 532 | return answer; 533 | } 534 | ``` 535 | 536 | * More simplified version 537 | 538 | ```java 539 | public int[] productExceptSelf(int[] nums) { 540 | int[] result = new int[nums.length]; 541 | for (int i = 0, tmp = 1; i < nums.length; i++) { 542 | result[i] = tmp; 543 | tmp *= nums[i]; 544 | } 545 | for (int i = nums.length - 1, tmp = 1; i >= 0; i--) { 546 | result[i] *= tmp; 547 | tmp *= nums[i]; 548 | } 549 | return result; 550 | } 551 | ``` 552 | 553 | - Time complexity: $O(N)$ where N represents the number of elements in the input array. We use one iteration to construct the array L, and one to update the array answer. 554 | - Space complexity: $O(1)$ since don't use any additional array for our computations. The problem statement mentions that using the answer array doesn't add to the space complexity. -------------------------------------------------------------------------------- /~$LeeCode Summary.xlsx.xlsx: -------------------------------------------------------------------------------- 1 | Microsoft Office User Microsoft Office User --------------------------------------------------------------------------------