├── .husky ├── .gitignore └── pre-commit ├── .gitignore ├── .prettierrc.json ├── .eslintrc.yml ├── .vscode ├── extensions.json ├── settings.json ├── launch.json └── leetcode-patterns.code-snippets ├── .editorconfig ├── package.json ├── 2_two-pointers ├── README.md ├── 167_two-sum-ii-input-array-is-sorted.js ├── 26_remove-duplicates-from-sorted-array.js ├── 16_3sum-closest.js ├── 844_backspace-string-compare.js ├── 259_3sum-smaller.js ├── 75_sort-colors.js ├── 18_4sum.js └── 581_shortest-unsorted-continuous-subarray.js ├── 6_in-place-reversal-of-a-linked-list ├── README.md ├── 206_reverse-linked-list.js ├── 92_reverse-linked-list-ii.js ├── 0_reverse-alternating-k-group.js └── 61_rotate-list.js ├── 7_BFS ├── README.md ├── 637_average-of-levels-in-binary-tree.js ├── 0_connect-all-level-order-siblings.js ├── 0_level-order-successor.js ├── 102_binary-tree-level-order-traversal.js ├── 111_minimum-depth-of-binary-tree.js └── 103_binary-tree-zigzag-level-order-traversal.js ├── 5_cyclic-sort ├── README.md ├── 0_cyclic-sort.js ├── 268_missing-number.js ├── 448_find-all-numbers-disappeared-in-an-array.js ├── 41_first-smallest-missing-positive.js ├── 442_find-all-duplicates-in-an-array.js ├── 0_find-the-corrupt-pair.js └── 0_find-first-k-missing-positive-numbers.js ├── 17_divide-and-conquer └── README.md ├── LICENSE ├── 12_bitwise-xor ├── README.md ├── 136_single-number.js ├── 268_missing-number.js ├── 1009_complement-of-base-10-integer.js ├── 82_flipping-an-image.js └── 260_single-number-iii.js ├── README.md ├── 3_fast-slow-pointers ├── README.md ├── 876_middle-of-the-linked-list.js ├── 141_linked-list-cycle.js ├── 202_happy-number.js ├── 142_linked-list-cycle-ii.js ├── 143_reorder-list.js └── 234_palindrome-linked-list.js ├── 16_topological-sort └── README.md ├── 11_binary-search ├── 0_ceiling-of-a-number.js ├── 0_order-agnostic-binary-search.js ├── 153_find-minimum-in-rotated-sorted-array.js ├── 0_minimum-difference-element.js ├── 744_find-smallest-letter-greater-than-target.js ├── 34_find-first-and-last-position-of-element-in-sorted-array.js ├── 81_search-in-rotated-sorted-array-ii.js ├── 0_search-bitonic-array.js ├── 162_bitonic-array-maximum.js ├── 154_find-minimum-in-rotated-sorted-array-ii.js └── 33_search-in-rotated-sorted-array.js ├── 1_sliding-window ├── 0_maximum-sum-subarray-of-size-k.js ├── README.md ├── 0_smallest-subarray-with-given-sum.js ├── 1004_max-consecutive-ones-iii.js ├── 3_longest-substring-without-repeating-characters.js ├── 340_longest-substring-with-at-most-k-distinct-characters.js ├── 438_find-all-anagrams-in-a-string.js ├── 904_fruit-into-baskets.js ├── 713_subarray-product-less-than-k.js ├── 30_substring-with-concatenation-of-all-words.js └── 76_minimum-window-substring.js ├── 13_top-k-elements ├── README.md ├── 347_top-k-frequent-elements.js ├── 451_sort-characters-by-frequency.js ├── 0_sum-of-elements-between-k1-and-k2.js ├── 1167_minimum-cost-to-connect-sticks.js ├── 973_k-closest-points-to-origin.js ├── 215_kth-largest-element-in-an-array.js └── 703_kth-largest-element-in-a-stream.js ├── 4_merge-intervals ├── 0_conflicting-appointments.js ├── 56_merge-intervals.js ├── 0_maximum-cpu-load.js ├── README.md ├── 57_insert-interval.js ├── 253_meeting-rooms-ii.js └── 986_interval-list-intersections.js ├── 14_k-way-merge ├── README.md ├── 0_kth-smallest-number-in-m-sorted-lists.js └── 23_merge-k-sorted-lists.js ├── 8_DFS ├── 112_path-sum.js ├── 1430_check-if-a-string-is-a-valid-sequence-from-root-to-leaves-path-in-a-binary-tree.js ├── README.md ├── 129_sum-root-to-leaf-numbers.js ├── 113_path-sum-ii.js └── 437_path-sum-iii.js ├── 9_two-heaps ├── README.md └── 480_sliding-window-median.js ├── 15_dynamic-programming └── README.md └── 10_subsets ├── 22_generate-parentheses.js ├── 784_letter-case-permutation.js ├── 46_permutations.js ├── README.md ├── 90_subsets-ii.js ├── 40_combination-sum-ii.js └── 320_generalized-abbreviation.js /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | es2021: true 4 | extends: 5 | - eslint:recommended 6 | - prettier 7 | parserOptions: 8 | ecmaVersion: 12 9 | rules: 10 | no-constant-condition: 11 | - off 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "streetsidesoftware.code-spell-checker", 4 | "editorconfig.editorconfig", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "bitonic", 4 | "bsts", 5 | "heapify", 6 | "leetcode", 7 | "nums", 8 | "subarray" 9 | ], 10 | "editor.defaultFormatter": "esbenp.prettier-vscode", 11 | "[javascript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leetcode-patterns", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:PinkyJie/leetcode-patterns.git", 6 | "author": "Wenbo Jie ", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "eslint": "^7.28.0", 10 | "eslint-config-prettier": "^8.3.0", 11 | "husky": "^6.0.0", 12 | "lint-staged": "^11.0.0", 13 | "prettier": "^2.3.1" 14 | }, 15 | "lint-staged": { 16 | "**/*": "prettier --write --ignore-unknown", 17 | "**/*.js": "eslint" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-node", 9 | "request": "launch", 10 | "name": "Run Current", 11 | "skipFiles": ["/**"], 12 | "program": "${workspaceFolder}/${relativeFile}", 13 | "internalConsoleOptions": "openOnSessionStart" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /2_two-pointers/README.md: -------------------------------------------------------------------------------- 1 | # Two pointers 2 | 3 | ## When to use 4 | 5 | In problems where we deal with sorted arrays (or LinkedLists) and need to find a set of elements that fulfill certain constraints, the Two Pointers approach becomes quite useful. The set of elements could be a pair, a triplet or even a subarray. 6 | 7 | ## Pseudo code 8 | 9 | There is no "Pseudo code" for two pointer problems, the things we need to figure out while using the approach: 10 | 11 | - different kinds of the two pointers: 12 | - left pointer starts from index 0 and right pointer starts from the last index 13 | - one pointer is slow and one pointer is fast 14 | - when to increase/decrease the two pointers 15 | -------------------------------------------------------------------------------- /6_in-place-reversal-of-a-linked-list/README.md: -------------------------------------------------------------------------------- 1 | # In-place reversal of a Linked List 2 | 3 | ## When to use 4 | 5 | For problems which requires us to reverse the whole linked list or partial of the linked list (sub list) in-place, i.e. using the existing node objects and without using extra memory. 6 | 7 | ## Pseudo code 8 | 9 | ```javascript 10 | let prev = null; 11 | let current = head; 12 | while (current) { 13 | const next = current.next; 14 | current.next = prev; 15 | prev = current; 16 | current = next; 17 | } 18 | return prev; 19 | ``` 20 | 21 | The key is to remember: 22 | 23 | - in every loop we only handle the pointer reversal between `current` node and `prev`, i.e. `current.next = prev`. 24 | - after reversal, the `prev` pointer is the start of the reversed linked list, `current` is the the next pointer of the original end (e.g. `null`) 25 | -------------------------------------------------------------------------------- /7_BFS/README.md: -------------------------------------------------------------------------------- 1 | # Breath First Search 2 | 3 | ## When to use 4 | 5 | Any problem involving the traversal of a tree in a level-by-level order can be efficiently solved by using this approach. We will use a `Queue` to keep track of all the nodes of a level before we jump onto the next level. This also means that the space complexity of the algorithm will be `O(W)`, where `W` is the maximum number of nodes on any level. 6 | 7 | ## Pseudo code 8 | 9 | ```javascript 10 | const levelNodes = [root]; 11 | while (levelNodes.length > 0) { 12 | const levelLength = levelNodes.length; 13 | for (let i = 0; i < levelLength; i++) { 14 | const node = levelNodes.shift(); 15 | aggregateLevelNode(node); 16 | if (node.left) { 17 | levelNodes.push(node.left); 18 | } 19 | if (node.right) { 20 | levelNodes.push(node.right); 21 | } 22 | } 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /5_cyclic-sort/README.md: -------------------------------------------------------------------------------- 1 | # Cyclic sort 2 | 3 | ## When to use 4 | 5 | For problems involving arrays containing numbers in a given range (e.g. from 1 to n), to find the duplicate numbers or missing numbers in the array. 6 | 7 | ## Pseudo code 8 | 9 | ```javascript 10 | for (let i = 0; i < numbers.length; i++) { 11 | // keep swapping if index i doesn't hold the correct number 12 | while (numbers[i] !== i + 1) { 13 | if (numbers[numbers[i] - 1] !== numbers[i]) { 14 | _swap(numbers, i, numbers[i] - 1); 15 | } 16 | } 17 | } 18 | ``` 19 | 20 | The common strategy is to keep swapping the number on current index to its correct position (index which matches the number) until the correct number (which matches the index) to be swapped to the current index. 21 | 22 | Note: for out of bound numbers (e.g. negative or larger than n), we simply skip them and they will be swapped to the end of the array (check [first-smallest-missing-positive](41_first-smallest-missing-positive.js)). 23 | -------------------------------------------------------------------------------- /17_divide-and-conquer/README.md: -------------------------------------------------------------------------------- 1 | # Divide and conquer 2 | 3 | ## When to use 4 | 5 | If a problem is related to a collection and can be solved in the same way when the collection is split into sub problems, we can try to use divide-and-conquer approach to solve it. Basically we can try to split the collection first to 2 parts, and then combine the results together. 6 | 7 | ## Pseudo code 8 | 9 | ```javascript 10 | let results = []; 11 | for (let i = 0; i < splitIndices.length; i++) { 12 | const leftResult = splitWithWay(0, splitIndices[i]); 13 | const rightResult = splitWithWay(splitIndices[i], n); 14 | const result = combineResult(leftResult, rightResult); 15 | results = results.push(result); 16 | } 17 | ``` 18 | 19 | From the above code we can see, things need to be considered when using this approach: 20 | 21 | - how should we split the collection? (`splitIndices`) 22 | - how to combine the `leftResult` and `rightResult` (`combineResult`) 23 | 24 | Note: consider to use memoization sometimes cause different splitting methods can generate duplicate sub problems. 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Wenbo Jie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /12_bitwise-xor/README.md: -------------------------------------------------------------------------------- 1 | # Bitwise XOR 2 | 3 | XOR is a logical bitwise operator that returns 0 (false) if both bits are the same and returns 1 (true) otherwise. In other words, it only returns 1 if exactly one bit is set to 1 out of the two bits in comparison. 4 | 5 | Some useful properties of XOR operation: 6 | 7 | - Taking XOR of a number with itself returns 0, e.g. 8 | 9 | ```javascript 10 | 1 ^ 1 = 0; 11 | 29 ^ 29 = 0; 12 | ``` 13 | 14 | - Taking XOR of a number with 0 returns the same number, e.g. 15 | 16 | ```javascript 17 | 1 ^ 0 = 1; 18 | 31 ^ 0 = 31; 19 | ``` 20 | 21 | - XOR is Associative & Commutative, which means: 22 | 23 | ```javascript 24 | a ^ b ^ c = a ^ (b ^ c); 25 | a ^ b = b ^ a; 26 | ``` 27 | 28 | ## When to use 29 | 30 | XOR is especially useful to solve the following problems: 31 | 32 | - handle the "Missing numbers in a range" problems, because XOR can eliminate the duplicate pair easily. 33 | - change 1 to 0 and change 0 to 1, because we have `1 ^ 1 = 0` and `0 ^ 1 = 1`. 34 | 35 | Note: other useful bitwise operations: 36 | 37 | - get rightmost set bit: `n & (-n)`, e.g. `10` -> `110` (binary) -> `2` (first 1 bit from the right side) 38 | -------------------------------------------------------------------------------- /12_bitwise-xor/136_single-number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * In a non-empty array of integers, every number appears twice except for one, find 5 | * that single number. 6 | * https://leetcode.com/problems/single-number/ 7 | * 8 | * Example 1: 9 | * Input: [1, 4, 2, 1, 3, 2, 3] 10 | * Output: 4 11 | * 12 | * Example 2: 13 | * Input: [7, 9, 7] 14 | * Output: 9 15 | * 16 | * 17 | * Time: O(n) 18 | * Space: O(1) 19 | * 20 | * @param {number[]} nums 21 | * @return {number} 22 | */ 23 | function findSingleNumber(nums) { 24 | /** 25 | * Similar as problem 268_missing-number, we just simply XOR all numbers in the array, 26 | * the identical numbers will become 0 after XOR, only the single number will remain. 27 | * 28 | * Note: we can also solve this problem by storing the count of each number in a map 29 | * and loop that map after to find whose count is 1, but the downside is it requires 30 | * additional space for the map. 31 | */ 32 | let xorResult = 0; 33 | for (let i = 0; i < nums.length; i++) { 34 | xorResult ^= nums[i]; 35 | } 36 | return xorResult; 37 | } 38 | 39 | // Test 40 | console.log(findSingleNumber([1, 4, 2, 1, 3, 2, 3])); // 4 41 | console.log(findSingleNumber([7, 9, 7])); // 9 42 | -------------------------------------------------------------------------------- /5_cyclic-sort/0_cyclic-sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array containing all the number from 1 to n without any duplicates, write 5 | * a function to sort the array in-place in O(n) and without any extra space. 6 | * 7 | * Example 1: 8 | * Input: [3, 1, 5, 4, 2] 9 | * Output: [1, 2, 3, 4, 5] 10 | * 11 | * Example 2: 12 | * Input: [2, 6, 4, 3, 1, 5] 13 | * Output: [1, 2, 3, 4, 5, 6] 14 | * 15 | * Time: O(n) 16 | * Space: O(1) 17 | * 18 | * @param {number[]} nums 19 | * @return {number[]} 20 | */ 21 | function cyclicSort(nums) { 22 | for (let i = 0; i < nums.length; i++) { 23 | /** 24 | * If index i doesn't hold its correct number `i + 1`, swap it to its 25 | * correct index `number[i] - 1`, keep swapping until number `i + 1` is 26 | * swapped to index i. 27 | * 28 | * Each swap will guarantee that one number is being placed to its correct 29 | * position, so the sorting can be finished with O(n) time. 30 | */ 31 | while (nums[i] != i + 1) { 32 | _swap(nums, i, nums[i] - 1); 33 | } 34 | } 35 | return nums; 36 | } 37 | 38 | function _swap(array, i, j) { 39 | [array[i], array[j]] = [array[j], array[i]]; 40 | } 41 | 42 | // Test 43 | console.log(cyclicSort([3, 1, 5, 4, 2])); // [1, 2, 3, 4, 5] 44 | console.log(cyclicSort([2, 6, 4, 3, 1, 5])); // [1, 2, 3, 4, 5, 6] 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leetcode-patterns 2 | 3 | Leetcode problems categorized by patterns. 4 | 5 | ## Patterns 6 | 7 | 1. [Sliding window](./1_sliding-window) 8 | 2. [Tow pointers](./2_two-pointers) 9 | 3. [Fast slow pointers](./3_fast-slow-pointers) 10 | 4. [Merge intervals](./4_merge-intervals) 11 | 5. [Cyclic sort](./5_cyclic-sort) 12 | 6. [In place reversal of a linked list](./6_in-place-reversal-of-a-linked-list) 13 | 7. [BFS](./7_BFS) 14 | 8. [DFS](./8_DFS) 15 | 9. [Two heaps](./9_two-heaps) 16 | 10. [Subsets](./10_subsets) 17 | 11. [Binary search](./11_binary-search) 18 | 12. [Bitwise XOR](./12_bitwise-xor) 19 | 13. [Top K elements](./13_top-k-elements) 20 | 14. [K-way merge](./14_k-way-merge) 21 | 15. [Dynamic programming](./15_dynamic-programming) 22 | 16. [Topological sort](./16_topological-sort) 23 | 17. [Divide and conquer](./17_divide-and-conquer) 24 | 25 | > If a file has a non-zero prefix file name, that means the problem can be found on Leetcode, there should be a link in the comment to link to Leetcode. If the problems can be found on Leetcode, then the solution inside is already validated against Leetcode (expect the ones require subscription). Files prefixed by 0 can not be found on Leetcode. 26 | 27 | ## Acknowledgement 28 | 29 | - [Grokking the Coding Interview](https://www.educative.io/courses/grokking-the-coding-interview) from Educative.io. 30 | - [labuladong 的算法小抄](https://labuladong.gitbook.io/algo/) (Chinese language) 31 | -------------------------------------------------------------------------------- /3_fast-slow-pointers/README.md: -------------------------------------------------------------------------------- 1 | # Fast & Slow pointers 2 | 3 | ## When to use 4 | 5 | The Fast & Slow pointer approach, is a pointer algorithm that uses two pointers which move through the array (or sequence/LinkedList) at different speeds. This approach is quite useful when dealing with cyclic LinkedLists or arrays. By moving at different speeds (say, in a cyclic LinkedList), the algorithm proves that the two pointers are bound to meet. The fast pointer should catch the slow pointer once both the pointers are in a cyclic loop. 6 | 7 | ## Pseudo code 8 | 9 | ```javascript 10 | let slowPointer = head; 11 | let fastPointer = head; 12 | 13 | while (fastPointer && fastPointer.next) { 14 | slowPointer = slowPointer.next; 15 | fastPointer = fastPointer.next.next; 16 | 17 | if (slowPointer === fastPointer) { 18 | // cycle found 19 | } 20 | } 21 | ``` 22 | 23 | ## Common usage for two pointers in the linked list 24 | 25 | - fast(2-step) and slow(1-step) pointers, move them at the 2-step and 1-step speed, if they can meet each other, then a cycle exists in the linked list. 26 | - pointer1 (at head) and pointer2 (at the meeting point described above), move pointer1 and pointer2 at the same speed (1-step), their meeting point is the start node of the cycle. (Why? check [142_linked-list-cycle-ii](./142_linked-list-cycle-ii.js)) 27 | - fast(2-step) and slow(1-step) pointers, when fast pointer reaches the end of the linked list, slow pointer is at the middle position of the linked list. 28 | -------------------------------------------------------------------------------- /3_fast-slow-pointers/876_middle-of-the-linked-list.js: -------------------------------------------------------------------------------- 1 | const { buildLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given the head of a Singly LinkedList, write a method to return the middle node of 7 | * the LinkedList. If the total number of nodes in the LinkedList is even, return the 8 | * second middle node. 9 | * https://leetcode.com/problems/middle-of-the-linked-list/ 10 | * 11 | * Example 1: 12 | * Input: 1 -> 2 -> 3 -> 4 -> 5 -> null 13 | * Output: 3 14 | * 15 | * Example 2: 16 | * Input: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> null 17 | * Output: 4 18 | * 19 | * Time: O(n) 20 | * Space: O(1) 21 | * 22 | * @param {ListNode} head 23 | * @return {ListNode} 24 | */ 25 | function findMiddleOfLinkedList(head) { 26 | let slowPointer = head; 27 | let fastPointer = head; 28 | /** 29 | * When fast pointer (2-step) reach the end, the slow pointer's position is at 30 | * the middle of the linked list. 31 | */ 32 | while (fastPointer && fastPointer.next) { 33 | slowPointer = slowPointer.next; 34 | fastPointer = fastPointer.next.next; 35 | } 36 | return slowPointer; 37 | } 38 | 39 | // Test 40 | const head1 = buildLinkedList([1, 2, 3, 4, 5]); 41 | console.log(findMiddleOfLinkedList(head1).val); // 3 42 | 43 | const head2 = buildLinkedList([1, 2, 3, 4, 5, 6]); 44 | console.log(findMiddleOfLinkedList(head2).val); // 4 45 | 46 | const head3 = buildLinkedList([1, 2, 3, 4, 5, 6, 7]); 47 | console.log(findMiddleOfLinkedList(head3).val); // 4 48 | -------------------------------------------------------------------------------- /2_two-pointers/167_two-sum-ii-input-array-is-sorted.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of sorted numbers and a target sum, find a pair in the array whose sum 5 | * is equal to the given target. Write a function to return the indices (1-based index) 6 | * of the two numbers (i.e. the pair) such that they add up to the given target. 7 | * https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/ 8 | * 9 | * Example 1: 10 | * Input: [1, 2, 3, 4, 6], target=6 11 | * Output: [2, 4] 12 | * Explanation: The numbers at index 2 and 4 add up to 6: 2+4=6 13 | * 14 | * Example 2: 15 | * Input: [2, 5, 9, 11], target=11 16 | * Output: [1, 3] 17 | * Explanation: The numbers at index 1 and 3 add up to 11: 2+9=11 18 | * 19 | * Time: O(n) 20 | * Space: O(1) 21 | * 22 | * @param {number[]} sortedArray 23 | * @param {number} targetSum 24 | * @return {number[]} 25 | */ 26 | function twoSumIIInputArrayIsSorted(sortedArray, targetSum) { 27 | let left = 0; 28 | let right = sortedArray.length - 1; 29 | 30 | while (left < right) { 31 | const sum = sortedArray[left] + sortedArray[right]; 32 | if (sum === targetSum) { 33 | return [left + 1, right + 1]; // index is 1-based as requested 34 | } 35 | if (sum < targetSum) { 36 | left++; 37 | } else { 38 | right--; 39 | } 40 | } 41 | return []; 42 | } 43 | 44 | // Test 45 | console.log(twoSumIIInputArrayIsSorted([1, 2, 3, 4, 6], 6)); // [2, 4] 46 | console.log(twoSumIIInputArrayIsSorted([2, 5, 9, 11], 11)); // [1, 3] 47 | -------------------------------------------------------------------------------- /16_topological-sort/README.md: -------------------------------------------------------------------------------- 1 | # Topological sort 2 | 3 | ## When to use 4 | 5 | Topological Sort is used to find a linear ordering of elements that have dependencies on each other. For example, if event B is dependent on event A, A comes before B in topological ordering. When the problem is asking for the order of dependent vertices in a directed graph, or the problem itself can be represented as directed graph. 6 | 7 | ## Pseudo code 8 | 9 | ```javascript 10 | // Step 1: build a graph and calculate in degrees 11 | const inDegrees = new Array(vertices).fill(0); 12 | const graph = new Array(vertices).fill(0).map(() => new Array()); 13 | for (let i = 0; i < edges.length; i++) { 14 | const [parent, child] = edges[i]; 15 | graph[parent].push(child); 16 | inDegrees[child]++; 17 | } 18 | 19 | // Step 2: find the initial 0 in degree vertices as start 20 | const sources = []; 21 | for (let i = 0; i < inDegrees.length; i++) { 22 | if (inDegrees[i] === 0) { 23 | sources.push(i); 24 | } 25 | } 26 | 27 | // Step 3: keep removing the 0 in-degree vertices and update inDegrees 28 | const result = []; 29 | while (sources.length > 0) { 30 | const source = sources.shift(); 31 | result.push(source); 32 | graph[source].forEach((child) => { 33 | inDegrees[child]--; 34 | if (inDegrees[child] === 0) { 35 | sources.push(child); 36 | } 37 | }); 38 | } 39 | 40 | // Step 4: If there's a cycle, topological sort is not possible 41 | if (result.length !== vertices) { 42 | return []; 43 | } 44 | 45 | return result; 46 | ``` 47 | -------------------------------------------------------------------------------- /11_binary-search/0_ceiling-of-a-number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of numbers sorted in an ascending order, find the ceiling of a given 5 | * number 'key'. The ceiling of the 'key' will be the smallest element in the given array 6 | * greater than or equal to the 'key'. Write a function to return the index of the 7 | * ceiling of the 'key'. If there isn’t any ceiling return -1. 8 | * 9 | * Example 1: 10 | * Input: [4, 6, 10], key = 6 11 | * Output: 1 12 | * Explanation: The smallest number greater than or equal to '6' is '6' having index '1'. 13 | * 14 | * Example 2: 15 | * Input: [1, 3, 8, 10, 15], key = 12 16 | * Output: 4 17 | * Explanation: The smallest number greater than or equal to '12' is '15' having index 18 | * '4'. 19 | * 20 | * 21 | * Time: O(log n) 22 | * Space: O(1) 23 | * 24 | * @param {number[]} arr 25 | * @param {number} key 26 | * @return {number} 27 | */ 28 | function ceilingOfNumber(arr, key) { 29 | let start = 0; 30 | let end = arr.length; 31 | while (start < end) { 32 | const middle = start + Math.floor((end - start) / 2); 33 | if (arr[middle] >= key) { 34 | end = middle; 35 | } else { 36 | start = middle + 1; 37 | } 38 | } 39 | if (start < arr.length) { 40 | return start; 41 | } 42 | return -1; 43 | } 44 | 45 | // Test 46 | console.log(ceilingOfNumber([4, 6, 10], 6)); // 1 47 | console.log(ceilingOfNumber([1, 3, 8, 10, 15], 12)); // 4 48 | console.log(ceilingOfNumber([4, 6, 10], 17)); // -1 49 | console.log(ceilingOfNumber([4, 6, 10], -1)); // 0 50 | -------------------------------------------------------------------------------- /1_sliding-window/0_maximum-sum-subarray-of-size-k.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of positive numbers and a positive number k, find the maximum sum of 5 | * any contiguous subarray of size k. 6 | * 7 | * Example 1: 8 | * Input: [2, 1, 5, 1, 3, 2], k=3 9 | * Output: 9 10 | * Explanation: Subarray with maximum sum is [5, 1, 3]. 11 | * 12 | * Example 2: 13 | * Input: [2, 3, 4, 1, 5], k=2 14 | * Output: 7 15 | * Explanation: Subarray with maximum sum is [3, 4]. 16 | * 17 | * Time: O(n) 18 | * Space: O(1) 19 | * 20 | * @param {number[]} array 21 | * @param {number} k 22 | * @return {number} 23 | */ 24 | function maximumSumSubarrayOfSizeK(array, k) { 25 | let windowStart = 0; 26 | let windowEnd = 0; 27 | 28 | let windowSum = 0; 29 | let maxWindowSum = -1; 30 | 31 | while (windowEnd < array.length) { 32 | // Aggregation is the sum of the window 33 | windowSum += array[windowEnd]; 34 | windowEnd++; 35 | 36 | /** 37 | * Shrink the window when the window size is larger than k. 38 | * 39 | * Note: 40 | * * `windowEnd` here is not in the window yet 41 | * * we can use `while` here but the loop will run at most once, same as `if` 42 | */ 43 | if (windowEnd >= k) { 44 | maxWindowSum = Math.max(maxWindowSum, windowSum); 45 | windowSum -= array[windowStart]; 46 | windowStart++; 47 | } 48 | } 49 | 50 | return maxWindowSum; 51 | } 52 | 53 | // Test 54 | console.log(maximumSumSubarrayOfSizeK([2, 1, 5, 1, 3, 2], 3)); // 9 55 | console.log(maximumSumSubarrayOfSizeK([2, 3, 4, 1, 5], 2)); // 7 56 | -------------------------------------------------------------------------------- /13_top-k-elements/README.md: -------------------------------------------------------------------------------- 1 | # Top K elements 2 | 3 | ## When to use 4 | 5 | Any problem that asks us to find the top/smallest/frequent K elements among a given set falls under this pattern. The best data structure that comes to mind to keep track of K elements is Heap. This pattern will make use of the Heap to solve multiple problems dealing with K elements at a time from a set of given elements. 6 | 7 | The "Pseudo code" for this would be the normal push/pop operation for the heap, the things we need to figure out while using the approach: 8 | 9 | - Max heap or Min heap? 10 | - We can always think in this way, push the first K elements in the array to the heap, in order to make the numbers in the heap are the final result, e.g. if we are looking for the top K largest elements, we can assume they are the answer, then for the remaining elements, we only need to push a number to the heap **when it's larger than the smallest number in the heap** (because this number should be the K largest rather than the smallest one), that's why we should use min heap. 11 | - For top K largest elements related problems, use min heap (check [215_kth-largest-element-in-an-array](./215_kth-largest-element-in-an-array.js) for more explanation) 12 | - For top K smallest elements related problems, use max heap (check [0_kth-smallest-element-in-an-array](./0_kth-smallest-element-in-an-array.js) for more explanation) 13 | - Pay attention to the comparator used in the heap, sometimes multiple factors need to be taken into consideration (e.g. [13_top-k-elements/895_maximum-frequency-stack](./895_maximum-frequency-stack.js)) 14 | -------------------------------------------------------------------------------- /4_merge-intervals/0_conflicting-appointments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of intervals representing ‘N’ appointments, find out if a person 5 | * can attend all the appointments. 6 | * 7 | * Example 1: 8 | * Appointments: [[1,4], [2,5], [7,9]] 9 | * Output: false 10 | * Explanation: Since [1,4] and [2,5] overlap, a person cannot attend both of these 11 | * appointments. 12 | * 13 | * Example 2: 14 | * Appointments: [[6,7], [2,4], [8,12]] 15 | * Output: true 16 | * Explanation: None of the appointments overlap, therefore a person can attend all 17 | * of them. 18 | * 19 | * Time: O(nlog(n)) 20 | * Space: O(n) <- sorting 21 | * 22 | * @param {number[][]} appointments 23 | * @return {boolean} 24 | */ 25 | function canAttendAllAppointments(appointments) { 26 | // O(nlog(n)) 27 | appointments.sort((a, b) => a[0] - b[0]); 28 | // O(n) 29 | for (let i = 1; i < appointments.length; i++) { 30 | /** 31 | * After sorting by start, it's guaranteed that current start is equal or greater 32 | * than the previous start, if the current start is less than the previous end, 33 | * which means the current interval overlaps with the previous interval. 34 | */ 35 | if (appointments[i][0] < appointments[i - 1][1]) { 36 | return false; 37 | } 38 | } 39 | return true; 40 | } 41 | 42 | // Test 43 | console.log( 44 | canAttendAllAppointments([ 45 | [1, 4], 46 | [2, 5], 47 | [7, 9], 48 | ]) 49 | ); // false 50 | console.log( 51 | canAttendAllAppointments([ 52 | [6, 7], 53 | [2, 4], 54 | [8, 12], 55 | ]) 56 | ); // true 57 | -------------------------------------------------------------------------------- /11_binary-search/0_order-agnostic-binary-search.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a sorted array of numbers, find if a given number key is present in the array. 5 | * Though we know that the array is sorted, we don't know if it's sorted in ascending or 6 | * descending order. You should assume that the array can have duplicates. Write a 7 | * function to return the index of the key if it is present in the array, otherwise 8 | * return -1. 9 | * 10 | * Example 1: 11 | * Input: [1, 2, 3, 3, 5, 6, 7], key = 5 12 | * Output: 4 13 | * 14 | * Example 2: 15 | * Input: [10, 6, 4], key = 10 16 | * Output: 0 17 | * 18 | * 19 | * Time: O(log n) 20 | * Space: O(1) 21 | * 22 | * @param {number[]} arr 23 | * @param {number} key 24 | * @return {number} 25 | */ 26 | function binarySearchWithUnknownOrder(arr, key) { 27 | const isAscending = arr[0] < arr[arr.length - 1]; 28 | let start = 0; 29 | let end = arr.length; 30 | while (start < end) { 31 | const middle = start + Math.floor((end - start) / 2); 32 | if (isAscending) { 33 | if (arr[middle] >= key) { 34 | end = middle; 35 | } else { 36 | start = middle + 1; 37 | } 38 | } else { 39 | if (arr[middle] <= key) { 40 | end = middle; 41 | } else { 42 | start = middle + 1; 43 | } 44 | } 45 | } 46 | return arr[start] === key ? start : -1; 47 | } 48 | 49 | // Test 50 | console.log(binarySearchWithUnknownOrder([1, 2, 3, 3, 5, 6, 7], 5)); // 4 51 | console.log(binarySearchWithUnknownOrder([10, 6, 4], 10)); // 0 52 | console.log(binarySearchWithUnknownOrder([10, 8, 4], 3)); // -1 53 | -------------------------------------------------------------------------------- /1_sliding-window/README.md: -------------------------------------------------------------------------------- 1 | # Sliding window 2 | 3 | ## When to use 4 | 5 | For problems which requires calculation/counting among all the contiguous sub arrays, we can try to maintain a sliding window with fixed size or dynamic size. 6 | 7 | ## Pseudo code 8 | 9 | ```javascript 10 | let windowStart = 0; 11 | let windowEnd = 0; 12 | 13 | // the initial value of aggregation on the sliding window [windowStart, windowEnd) 14 | let aggregationOnWindow = xxx; 15 | 16 | while (windowEnd < array.length) { 17 | // do something on `aggregationOnWindow` to update after adding `windowEnd` 18 | update(aggregationOnWindow, array[windowEnd]); 19 | // increase the window size by adding the `windowEnd` 20 | windowEnd++; 21 | 22 | // check if the window needs to shrink 23 | while (needsShrink()) { 24 | // do something on `aggregationOnWindow` to update after removing `windowStart` 25 | update(aggregationOnWindow, array[windowStart]); 26 | // decrease the window size by removing the `windowStart` 27 | windowStart++; 28 | } 29 | } 30 | ``` 31 | 32 | Based on the code above, for different problems we need to figure out 2 questions: 33 | 34 | - when to shrink the window 35 | - how to update the aggregation value 36 | - when to read the final aggregation value 37 | - if the `needShrink()` is a strict condition, e.g. the logic inside runs only the window itself is not valid, then aggregation value should be read after the inner while loop 38 | - if the `needShrink()` is a loose condition, e.g. the logic inside runs when the window itself is valid or larger (invalid), then aggregation value should be read inside the inner while loop 39 | -------------------------------------------------------------------------------- /5_cyclic-sort/268_missing-number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * We are given an array containing n distinct numbers taken from the range 0 to 5 | * n. Since the array has only n numbers out of the total n+1 numbers, find the 6 | * missing number. 7 | * https://leetcode.com/problems/missing-number/ 8 | * 9 | * Example 1: 10 | * Input: [4, 0, 3, 1] 11 | * Output: 2 12 | * 13 | * Example 2: 14 | * Input: [8, 3, 5, 2, 4, 6, 0, 1] 15 | * Output: 7 16 | * 17 | * Time: O(n) 18 | * Space: O(1) 19 | * 20 | * @param {number[]} nums 21 | * @return {number} 22 | */ 23 | function findMissingNumber(nums) { 24 | const n = nums.length; 25 | for (let i = 0; i < n; i++) { 26 | /** 27 | * Similar as the problem 0_cyclic-sort, the only difference here: 28 | * * the number starts from 0, so index i's correct number is number[i] 29 | * * we can't handle number n because the array length is n (holds 0~n), 30 | * so after swapping, either number n is at it's wrong position (the array 31 | * contains n), or all numbers are at its correct position (the array does 32 | * not contain n) 33 | */ 34 | while (nums[i] !== i && nums[i] < n) { 35 | _swap(nums, i, nums[i]); 36 | } 37 | } 38 | for (let i = 0; i < n; i++) { 39 | if (nums[i] === n) { 40 | return i; 41 | } 42 | } 43 | return n; 44 | } 45 | 46 | function _swap(array, i, j) { 47 | [array[i], array[j]] = [array[j], array[i]]; 48 | } 49 | 50 | // Test 51 | console.log(findMissingNumber([4, 0, 3, 1])); // 2 52 | console.log(findMissingNumber([8, 3, 5, 2, 4, 6, 0, 1])); // 7 53 | console.log(findMissingNumber([3, 1, 2, 0])); // 4 54 | -------------------------------------------------------------------------------- /7_BFS/637_average-of-levels-in-binary-tree.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree, populate an array to represent the averages of all of its 7 | * levels. 8 | * https://leetcode.com/problems/average-of-levels-in-binary-tree/ 9 | * 10 | * 11 | * Example 1: 12 | * Input: [1, 2, 3, 4, 5, 6, 7] 13 | * Output: [1, 2.5, 5.5] 14 | * 15 | * Example 2: 16 | * Input: [12, 7, 1, 9, 2, 10, 5] 17 | * Output: [12, 4, 6.5] 18 | * 19 | * 20 | * Time: O(n) 21 | * Space: O(n) 22 | * 23 | * @param {TreeNode} root 24 | * @return {number[]} 25 | */ 26 | function averageOfLevel(root) { 27 | if (!root) { 28 | return []; 29 | } 30 | 31 | const result = []; 32 | const levelNodes = [root]; 33 | while (levelNodes.length > 0) { 34 | const levelLength = levelNodes.length; 35 | let levelSum = 0; 36 | for (let i = 0; i < levelLength; i++) { 37 | const node = levelNodes.shift(); 38 | /** 39 | * Similar as 102_binary-tree-level-order-traversal, the only difference 40 | * is here we need to store the sum of each level nodes. 41 | */ 42 | levelSum += node.val; 43 | if (node.left) { 44 | levelNodes.push(node.left); 45 | } 46 | if (node.right) { 47 | levelNodes.push(node.right); 48 | } 49 | } 50 | result.push(levelSum / levelLength); 51 | } 52 | return result; 53 | } 54 | 55 | // Test 56 | const tree1 = buildTreeBFS([1, 2, 3, 4, 5, 6, 7]); 57 | console.log(averageOfLevel(tree1)); // [1, 2.5, 5.5] 58 | 59 | const tree2 = buildTreeBFS([12, 7, 1, 9, 2, 10, 5]); 60 | console.log(averageOfLevel(tree2)); // [12, 4, 6.5] 61 | -------------------------------------------------------------------------------- /5_cyclic-sort/448_find-all-numbers-disappeared-in-an-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * We are given an unsorted array containing numbers taken from the range 1 to n. 5 | * The array can have duplicates, which means some numbers will be missing. Find all 6 | * those missing numbers. 7 | * https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/ 8 | * 9 | * Example 1: 10 | * Input: [2, 3, 1, 8, 2, 3, 5, 1] 11 | * Output: 4, 6, 7 12 | * Explanation: The array should have all numbers from 1 to 8, due to duplicates 4, 13 | * 6, and 7 are missing. 14 | * 15 | * Example 2: 16 | * Input: [2, 4, 1, 2] 17 | * Output: 3 18 | * 19 | * Time: O(n) 20 | * Space: O(1) 21 | * 22 | * @param {number[]} nums 23 | * @return {number[]} 24 | */ 25 | function findAllMissingNumbers(nums) { 26 | for (let i = 0; i < nums.length; i++) { 27 | /** 28 | * Still similar as 0_cyclic-sort, but because we might have duplicates, 29 | * so when swapping, the 2 positions might have the same number, if that 30 | * happens, this index's corresponding number is missing, we should skip 31 | * that index. 32 | */ 33 | while (nums[i] !== i + 1 && nums[i] !== nums[nums[i] - 1]) { 34 | _swap(nums, i, nums[i] - 1); 35 | } 36 | } 37 | const result = []; 38 | for (let i = 0; i < nums.length; i++) { 39 | if (nums[i] !== i + 1) { 40 | result.push(i + 1); 41 | } 42 | } 43 | return result; 44 | } 45 | 46 | function _swap(array, i, j) { 47 | [array[i], array[j]] = [array[j], array[i]]; 48 | } 49 | 50 | // Test 51 | console.log(findAllMissingNumbers([2, 3, 1, 8, 2, 3, 5, 1])); // [4, 6, 7] 52 | console.log(findAllMissingNumbers([2, 4, 1, 2])); // [3] 53 | -------------------------------------------------------------------------------- /2_two-pointers/26_remove-duplicates-from-sorted-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of sorted numbers, remove all duplicates from it. You should not use any 5 | * extra space; after removing the duplicates in-place return the length of the subarray 6 | * that has no duplicate in it. 7 | * https://leetcode.com/problems/remove-duplicates-from-sorted-array/ 8 | * 9 | * Example 1: 10 | * Input: [2, 3, 3, 3, 6, 9, 9] 11 | * Output: 4 12 | * Explanation: The first four elements after removing the duplicates will be [2, 3, 6, 9]. 13 | * 14 | * Example 2: 15 | * Input: [2, 2, 2, 11] 16 | * Output: 2 17 | * Explanation: The first two elements after removing the duplicates will be [2, 11]. 18 | * 19 | * Time: O(n) 20 | * Space: O(1) 21 | * 22 | * @param {number[]} sortedArray 23 | * @return {number} 24 | */ 25 | function removeDuplicatesFromSortedArray(sortedArray) { 26 | const n = sortedArray.length; 27 | if (n <= 1) { 28 | return n; 29 | } 30 | 31 | // pointer 1: the index for last non-duplicate number 32 | let slow = 0; 33 | // pointer 2: the looping index run through all the numbers in the array 34 | let fast = 1; 35 | while (fast < n) { 36 | /** 37 | * Only increase `slow` when the current number (`fast`) is not equal to 38 | * the last number in the non-duplicate array (`slow`) 39 | */ 40 | if (sortedArray[fast] !== sortedArray[slow]) { 41 | slow++; 42 | sortedArray[slow] = sortedArray[fast]; 43 | } 44 | fast++; 45 | } 46 | return slow + 1; 47 | } 48 | 49 | // Test 50 | console.log(removeDuplicatesFromSortedArray([2, 3, 3, 3, 6, 9, 9])); // 4 51 | console.log(removeDuplicatesFromSortedArray([2, 2, 2, 11])); // 2 52 | -------------------------------------------------------------------------------- /5_cyclic-sort/41_first-smallest-missing-positive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an unsorted array containing numbers, find the smallest missing positive 5 | * number in it. 6 | * https://leetcode.com/problems/first-missing-positive/ 7 | * 8 | * Example 1: 9 | * Input: [-3, 1, 5, 4, 2] 10 | * Output: 3 11 | * Explanation: The smallest missing positive number is '3' 12 | * 13 | * Example 2: 14 | * Input: [3, -2, 0, 1, 2] 15 | * Output: 4 16 | * 17 | * Time: O(n) 18 | * Space: O(1) 19 | * 20 | * @param {number[]} nums 21 | * @return {number} 22 | */ 23 | function findSmallestMissingPositive(nums) { 24 | const n = nums.length; 25 | for (let i = 0; i < n; i++) { 26 | /** 27 | * Similar as 448_find-all-numbers-disappeared-in-an-array, the difference is 28 | * here we treat all numbers out of the 1~n range as invalid numbers (skip 29 | * them in the loop), so for the array after swap, the 1st mismatched number will 30 | * be the smallest positive missing number. 31 | */ 32 | while (nums[i] !== i + 1) { 33 | if (nums[i] !== nums[nums[i] - 1] && nums[i] >= 1 && nums[i] <= n) { 34 | _swap(nums, i, nums[i] - 1); 35 | } else { 36 | break; 37 | } 38 | } 39 | } 40 | for (let i = 0; i < n; i++) { 41 | if (nums[i] !== i + 1) { 42 | return i + 1; 43 | } 44 | } 45 | // if there's no mismatched number, then n+1 would be the result 46 | return n + 1; 47 | } 48 | 49 | function _swap(array, i, j) { 50 | [array[i], array[j]] = [array[j], array[i]]; 51 | } 52 | 53 | // Test 54 | console.log(findSmallestMissingPositive([-3, 1, 5, 4, 2])); // 3 55 | console.log(findSmallestMissingPositive([3, -2, 0, 1, 2])); // 4 56 | -------------------------------------------------------------------------------- /7_BFS/0_connect-all-level-order-siblings.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree, connect each node with its level order successor. The last 7 | * node of each level should point to the first node of the next level. 8 | * 9 | * 10 | * Time: O(n) 11 | * Space: O(n) 12 | * 13 | * Note: similar as 116_populating-next-right-pointers-in-each-node, this problem is 14 | * even simpler: we don't need to know the start of each level, just follow the 15 | * traditional BFS traversal, and update next accordingly. 16 | * 17 | * @param {TreeNode} root 18 | * @return {TreeNode} 19 | */ 20 | function connectAllLevelNodeSiblings(root) { 21 | if (!root) { 22 | return null; 23 | } 24 | 25 | const queue = [root]; 26 | let prev = null; 27 | while (queue.length > 0) { 28 | const node = queue.shift(); 29 | if (prev) { 30 | prev.next = node; 31 | } 32 | prev = node; 33 | if (node.left) { 34 | queue.push(node.left); 35 | } 36 | if (node.right) { 37 | queue.push(node.right); 38 | } 39 | } 40 | return root; 41 | } 42 | 43 | // Test 44 | const tree1 = buildTreeBFS([1, 2, 3, 4, 5, 6, 7]); 45 | const result1 = connectAllLevelNodeSiblings(tree1); 46 | _printNodesWithNext(result1); // [1, 2, 3, 4, 5, 6, 7] 47 | 48 | const tree2 = buildTreeBFS([12, 7, 1, null, 9, 10, 5]); 49 | const result2 = connectAllLevelNodeSiblings(tree2); 50 | _printNodesWithNext(result2); // [12, 7, 1, 9, 10, 5] 51 | 52 | function _printNodesWithNext(root) { 53 | let current = root; 54 | const values = []; 55 | while (current) { 56 | values.push(current.val); 57 | current = current.next; 58 | } 59 | console.log(values); 60 | } 61 | -------------------------------------------------------------------------------- /.vscode/leetcode-patterns.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | // Place your leetcode-patterns workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and 3 | // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope 4 | // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is 5 | // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: 6 | // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. 7 | // Placeholders with the same ids are connected. 8 | // Example: 9 | // "Print to console": { 10 | // "scope": "javascript,typescript", 11 | // "prefix": "log", 12 | // "body": [ 13 | // "console.log('$1');", 14 | // "$2" 15 | // ], 16 | // "description": "Log output to console" 17 | // } 18 | "Functions with Docstring": { 19 | "scope": "javascript", 20 | "prefix": "func", 21 | "description": "Declare a function with docstring.", 22 | "body": [ 23 | "/**", 24 | " *", 25 | " * Problem:", 26 | " *", 27 | " *", 28 | " * Example 1:", 29 | " * Input: ", 30 | " * Output: ", 31 | " * Explanation: ", 32 | " *", 33 | " * Example 2:", 34 | " * Input: ", 35 | " * Output: ", 36 | " * Explanation: ", 37 | " *", 38 | " *", 39 | " * Time:", 40 | " * Space:", 41 | " *", 42 | " * @param {*}", 43 | " * @return {*}", 44 | " */", 45 | "function $1() {", 46 | " $2", 47 | "}", 48 | "", 49 | "", 50 | "// Test", 51 | "console.log($1());" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /14_k-way-merge/README.md: -------------------------------------------------------------------------------- 1 | # K-way merge 2 | 3 | This pattern helps us solve problems that involve merging a list of sorted arrays. 4 | 5 | ## When to use 6 | 7 | Whenever we are given K sorted arrays, we can use a Heap to efficiently perform a sorted traversal of all the elements of all arrays. We can push the smallest (first) element of each sorted array into a Min Heap to get the overall minimum. While inserting elements to the Min Heap we keep track of which array the element came from. We can, then, remove the top element from the heap to get the smallest element and push the next element from the same array, to which this smallest element belonged, to the heap. We can repeat this process to make a sorted traversal of all elements. 8 | 9 | ## Pseudo code 10 | 11 | ```javascript 12 | // item in heap { value, listIndex, itemIndex } 13 | const minHeap = new Heap((a, b) => b.value - a.value); 14 | 15 | for (let i = 0; i < lists.length; i++) { 16 | minHeap.push({ value: lists[i][0], listIndex: i, itemIndex: 0 }); 17 | } 18 | 19 | while (minHeap.size() > 0) { 20 | const item = minHeap.pop(); 21 | const nextItemIndex = item.itemIndex + 1; 22 | if (nextItemIndex < lists[item.listIndex].length) { 23 | minHeap.push({ 24 | value: lists[item.listIndex][nextItemIndex], 25 | listIndex: listIndex, 26 | itemIndex: nextItemIndex, 27 | }); 28 | } 29 | } 30 | ``` 31 | 32 | Note: during this process, we can say the heap always contains one element from each array, until the popped element's original array doesn't have any other elements left. This property is useful when we need to calculate something which requires the heap at least contain one element from each array. (e.g. [632_smallest-range-covering-elements-from-k-lists](./632_smallest-range-covering-elements-from-k-lists.js)) 33 | -------------------------------------------------------------------------------- /7_BFS/0_level-order-successor.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree and a node, find the level order successor of the given node 7 | * in the tree. The level order successor is the node that appears right after the 8 | * given node in the level order traversal. 9 | * 10 | * 11 | * Example 1: 12 | * Input: [1, 2, 3, 4, 5], 3 13 | * Output: 4 14 | * 15 | * Example 2: 16 | * Input: [12, 7, 1, null, 9, 10, 5], 9 17 | * Output: 10 18 | * 19 | * 20 | * Time: O(n) 21 | * Space: O(n) 22 | * 23 | * @param {TreeNode} root 24 | * @param {number} nodeValue 25 | * @return {TreeNode} 26 | */ 27 | function levelOrderSuccessor(root, nodeValue) { 28 | if (!root) { 29 | return null; 30 | } 31 | 32 | const queue = [root]; 33 | while (queue.length > 0) { 34 | const node = queue.shift(); 35 | if (node.left) { 36 | queue.push(node.left); 37 | } 38 | if (node.right) { 39 | queue.push(node.right); 40 | } 41 | /** 42 | * Follow the traditional BFS traversal process, if the value is found, break 43 | * the loop. Note here we always push the children nodes to the queue first 44 | * before breaking, so when the value is found, its successor should already 45 | * in the queue (unless it's the last node), the first node in the queue is 46 | * what we want. 47 | */ 48 | if (node.val === nodeValue) { 49 | break; 50 | } 51 | } 52 | if (queue.length > 0) { 53 | return queue[0]; 54 | } 55 | return null; 56 | } 57 | 58 | // Test 59 | const tree1 = buildTreeBFS([1, 2, 3, 4, 5]); 60 | console.log(levelOrderSuccessor(tree1, 3).val); // 4 61 | 62 | const tree2 = buildTreeBFS([12, 7, 1, null, 9, 10, 5]); 63 | console.log(levelOrderSuccessor(tree2, 9).val); // 10 64 | -------------------------------------------------------------------------------- /4_merge-intervals/56_merge-intervals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a list of intervals, merge all the overlapping intervals to produce a list 5 | * that has only mutually exclusive intervals. 6 | * https://leetcode.com/problems/merge-intervals/ 7 | * 8 | * Example 1: 9 | * Intervals: [[1,4], [2,5], [7,9]] 10 | * Output: [[1,5], [7,9]] 11 | * Explanation: Since the first two intervals [1,4] and [2,5] overlap, we merged them 12 | * into one [1,5]. 13 | * 14 | * Example 2: 15 | * Intervals: [[6,7], [2,4], [5,9]] 16 | * Output: [[2,4], [5,9]] 17 | * Explanation: Since the intervals [6,7] and [5,9] overlap, we merged them into one 18 | * [5,9]. 19 | * 20 | * Time: O(nlog(n)) 21 | * Space: O(n) <- sorting 22 | * 23 | * @param {number[][]} intervals 24 | * @return {number[][]} 25 | */ 26 | function mergeIntervals(intervals) { 27 | if (intervals.length === 0) { 28 | return []; 29 | } 30 | 31 | // sort the intervals by their start O(nlog(n)) 32 | intervals.sort((a, b) => a[0] - b[0]); 33 | 34 | const result = [intervals[0]]; 35 | // O(n) 36 | for (let i = 1; i < intervals.length; i++) { 37 | const lastInterval = result[result.length - 1]; 38 | const newInterval = intervals[i]; 39 | if (newInterval[0] <= lastInterval[1]) { 40 | // overlap 41 | lastInterval[1] = Math.max(lastInterval[1], newInterval[1]); 42 | } else { 43 | // do not overlap 44 | result.push(newInterval); 45 | } 46 | } 47 | 48 | return result; 49 | } 50 | 51 | // Test 52 | const result1 = mergeIntervals([ 53 | [1, 4], 54 | [2, 5], 55 | [7, 9], 56 | ]); 57 | result1.forEach((i) => console.log(i)); // [[1,5], [7,9]] 58 | const result2 = mergeIntervals([ 59 | [6, 7], 60 | [2, 4], 61 | [5, 9], 62 | ]); 63 | result2.forEach((i) => console.log(i)); // [[2,4], [5,9]] 64 | -------------------------------------------------------------------------------- /8_DFS/112_path-sum.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree and a number S, find if the tree has a path from 7 | * root-to-leaf such that the sum of all the node values of that path equals S. 8 | * https://leetcode.com/problems/path-sum/ 9 | * 10 | * Example 1: 11 | * Input: [1, 2, 3, 4, 5, 6, 7], 10 12 | * Output: true 13 | * 14 | * Example 2: 15 | * Input: [12, 7, 1, null, 9, 10, 5], 16 16 | * Output: false 17 | * 18 | * 19 | * Time: O(n) 20 | * Space: O(h) (log(n) ~ n) <- recursion stack (height) 21 | * 22 | * @param {TreeNode} root 23 | * @param {number} sum 24 | * @return {boolean} 25 | */ 26 | function hasPathSum(root, sum) { 27 | if (!root) { 28 | return false; 29 | } 30 | /** 31 | * Do the check for each leaf node. 32 | */ 33 | if (!root.left && !root.right) { 34 | return sum === root.val; 35 | } 36 | /** 37 | * This is technically pre-order traversal, the `doVisit` here is to subtract 38 | * the current node value from the target sum. After visiting left child, we 39 | * should restore the sum to its original value before visiting right child 40 | * (because now the "root" is the parent), that's why we pass the same 41 | * `sum - root.val` for both children here. 42 | */ 43 | return ( 44 | hasPathSum(root.left, sum - root.val) || 45 | /** 46 | * Use "||" here because we want the traversal to stop immediately once we 47 | * find a matched path, e.g. no need to traverse the right half. 48 | */ 49 | hasPathSum(root.right, sum - root.val) 50 | ); 51 | } 52 | 53 | // Test 54 | const tree1 = buildTreeBFS([1, 2, 3, 4, 5, 6, 7]); 55 | console.log(hasPathSum(tree1, 10)); // true 56 | 57 | const tree2 = buildTreeBFS([12, 7, 1, null, 9, 10, 5]); 58 | console.log(hasPathSum(tree2, 16)); // false 59 | -------------------------------------------------------------------------------- /7_BFS/102_binary-tree-level-order-traversal.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree, populate an array to represent its level-by-level traversal. 7 | * You should populate the values of all nodes of each level from left to right in 8 | * separate sub-arrays. 9 | * https://leetcode.com/problems/binary-tree-level-order-traversal/ 10 | * 11 | * Example 1: 12 | * Input: [1, 2, 3, 4, 5, 6, 7] 13 | * Output: [[1], [2, 3], [4, 5, 6, 7]] 14 | * 15 | * Example 2: 16 | * Input: [12, 7, 1, null, 9, 10, 5] 17 | * Output: [[12], [7, 1], [9, 10, 5]] 18 | * 19 | * Time: O(n) <- need to traverse the whole tree, each node is visited exactly once 20 | * Space: O(n) <- the maximum node number of any level, i.e. n/2 21 | * 22 | * @param {TreeNode} root 23 | * @return {number[][]} 24 | */ 25 | function levelOrderTraverse(root) { 26 | if (!root) { 27 | return []; 28 | } 29 | 30 | const result = []; 31 | let levelNodes = [root]; 32 | while (levelNodes.length > 0) { 33 | const levelLength = levelNodes.length; 34 | const values = []; 35 | for (let i = 0; i < levelLength; i++) { 36 | const node = levelNodes.shift(); 37 | values.push(node.val); 38 | if (node.left) { 39 | levelNodes.push(node.left); 40 | } 41 | if (node.right) { 42 | levelNodes.push(node.right); 43 | } 44 | } 45 | result.push(values); 46 | } 47 | return result; 48 | } 49 | 50 | // Test 51 | const tree1 = buildTreeBFS([1, 2, 3, 4, 5, 6, 7]); 52 | const result1 = levelOrderTraverse(tree1); 53 | result1.forEach((i) => console.log(i)); // [[1], [2, 3], [4, 5, 6, 7]] 54 | 55 | const tree2 = buildTreeBFS([12, 7, 1, null, 9, 10, 5]); 56 | const result2 = levelOrderTraverse(tree2); 57 | result2.forEach((i) => console.log(i)); // [[12], [7, 1], [9, 10, 5]] 58 | -------------------------------------------------------------------------------- /9_two-heaps/README.md: -------------------------------------------------------------------------------- 1 | # Two heaps 2 | 3 | ## When to use 4 | 5 | In many problems, where we are given a set of elements such that we can divide them into two parts. To solve the problem, we are interested in knowing the smallest element in one part and the biggest element in the other part. This pattern is an efficient approach to solve such problems. 6 | 7 | This pattern uses two Heaps to solve these problems: A Min Heap to find the smallest element and a Max Heap to find the biggest element. 8 | 9 | ## Typical use cases 10 | 11 | 1. **Find the median**: split the number group into 2 sections, put the smaller part into a max heap, and put the larger part into a min heap, thus the median number will be calculated by the top of the max heap and the top of the min heap. (check [295_find-median-from-data-stream](./295_find-median-from-data-stream.js) for details.) 12 | 13 | - assume the smaller part can have one more items than the larger part, this is to cater the scenario when the whole group has odd counts 14 | - keep in mind to do re-balance after push/pop items to/from the heaps, make sure the length of the larger part equal or one less than the smaller part 15 | 16 | 2. **Optimization problems which involves 2 factors**: use 1 factor as the comparison condition for one heap and use the other factor for another heap. (check [502_ipo](./502_ipo.js) for details.) Usually we need to push all the items in one heap first, or sometimes both heaps (e.g. [436_find-right-interval](./436_find-right-interval.js)). 17 | 18 | - the key logic is for one item in heap1, put all eligible items for it into heap2, to make heap2 top is the optimal result we need 19 | - the 2 heaps can be one max and one min, or two mins, or two maxs according to the requirements, but usually the common strategy is to keep `pop()` the ineligible items in heap2 to make sure it only contains eligible items 20 | -------------------------------------------------------------------------------- /13_top-k-elements/347_top-k-frequent-elements.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given an unsorted array of numbers, find the top K frequently occurring numbers in 7 | * it. 8 | * https://leetcode.com/problems/top-k-frequent-elements/ 9 | * 10 | * Example 1: 11 | * Input: [1, 3, 5, 12, 11, 12, 11], K = 2 12 | * Output: [12, 11] 13 | * Explanation: Both '11' and '12' appeared twice. 14 | 15 | * 16 | * Example 2: 17 | * Input: [5, 12, 11, 3, 11], K = 2 18 | * Output: [11, 5] or [11, 12] or [11, 3] 19 | * Explanation: Only '11' appeared twice, all other numbers appeared once. 20 | * 21 | * 22 | * Time: O(n) + O(n log(k)) 23 | * Space: O(n) <- for heap and `freqMap` 24 | * 25 | * @param {number[]} nums 26 | * @param {number} k 27 | * @return {number[]} 28 | */ 29 | function findTopKFrequentElements(nums, k) { 30 | const freqMap = {}; 31 | for (let i = 0; i < nums.length; i++) { 32 | freqMap[nums[i]] = (freqMap[nums[i]] || 0) + 1; 33 | } 34 | /** 35 | * Similar as the problem 215_kth-largest-element-in-an-array, the difference here is 36 | * we need to store both the number and its frequency (comparison factor) in the heap. 37 | */ 38 | const minHeap = new Heap((a, b) => b[1] - a[1]); 39 | Object.keys(freqMap).forEach((num, index) => { 40 | if (index < k) { 41 | // note map's key is string, that's why we need to do conversion here 42 | minHeap.push([Number(num), freqMap[num]]); 43 | } else if (freqMap[num] > minHeap.peek()[1]) { 44 | minHeap.pop(); 45 | minHeap.push([Number(num), freqMap[num]]); 46 | } 47 | }); 48 | const array = minHeap.toArray(); 49 | return array.map((item) => item[0]); 50 | } 51 | 52 | // Test 53 | console.log(findTopKFrequentElements([1, 3, 5, 12, 11, 12, 11], 2)); // [11, 12] 54 | console.log(findTopKFrequentElements([5, 12, 11, 3, 11], 2)); // [5, 11] 55 | -------------------------------------------------------------------------------- /15_dynamic-programming/README.md: -------------------------------------------------------------------------------- 1 | # Dynamic programming 2 | 3 | ## When to use 4 | 5 | Dynamic programming is the common strategy used for optimization problems, e.g. problems asking for minimum/maximum values. 6 | 7 | ## Pseudo code 8 | 9 | The keys of dynamic programming can be summarized as: 10 | 11 | 1. **state**: what states are changing during the optimization? 12 | 2. **transition**: what choices are available for each state, and how to state transit? 13 | 3. **base state**: what is the base case of the state? 14 | 15 | ```javascript 16 | base[0][0][...] = baseCase; 17 | for (const state1 of choices1) { 18 | for (const state2 of choices2) { 19 | ... 20 | dp[state1][state2][...] = transition(dp[...][...][...]); 21 | } 22 | } 23 | ``` 24 | 25 | All dynamic programming problems can be solved with the 4 solutions mentioned there. (Check [0_0-1-knapsack](./0_0-1-knapsack.js) for details.) 26 | 27 | 1. The naive recursion which will end up with a exponential time complexity. 28 | 2. If there are duplicate sub-problems in solution 1, then use memoization to reduce the time complexity. The final complexity depends on how many states we need to consider in the problem, e.g. for 2 states we can construct a `memo` matrix and reduce the complexity to `O(mn)`. 29 | 3. Use the same analysis approach from solution 2, we can get the transition function to calculate the `memo` matrix from bottom to up (smaller index to larger index), thus we don't need to use recursion any more. Note: the time complexity will still be the same as solution 2, the space complexity is also the same expect we don't have recursion stack cost any more. 30 | 4. If solution 3's transition function has such properties: the new row values only depends on the previous row values, we can try to reduce the matrix to a single dimension array to reduce the space complexity. Note: the time complexity will still be the same as solution 3. 31 | -------------------------------------------------------------------------------- /12_bitwise-xor/268_missing-number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * We are given an array containing n distinct numbers taken from the range 0 to 5 | * n. Since the array has only n numbers out of the total n+1 numbers, find the 6 | * missing number. 7 | * https://leetcode.com/problems/missing-number/ 8 | * 9 | * Example 1: 10 | * Input: [4, 0, 3, 1] 11 | * Output: 2 12 | * 13 | * Example 2: 14 | * Input: [8, 3, 5, 2, 4, 6, 0, 1] 15 | * Output: 7 16 | * 17 | * Time: O(n) 18 | * Space: O(1) 19 | * 20 | * @param {number[]} nums 21 | * @return {number} 22 | */ 23 | function findMissingNumber(nums) { 24 | /** 25 | * The basic idea here is to XOR all numbers from range [0, n] (`n + 1` numbers) and 26 | * get `xorForRange`, then XOR all numbers from the array and get `xorForArray`, we 27 | * know the array is missing one number, and all the other numbers are identical in 28 | * these two arrays, so if we XOR these two result, all identical pairs will result 29 | * in 0, the answer will be the missing number. 30 | * 31 | * Note: the problem can also be solved by array summation, i.e. get the summation of 32 | * the whole range and minus all the numbers in the array, we can also get the missing 33 | * number. The time/space complexity is the same as this implementation, the only 34 | * downside for the summation method is it might be overflowed. 35 | */ 36 | const n = nums.length; 37 | // Always use 0 as the initial value of XOR result, because 0 ^ any = any 38 | let xorForRange = 0; 39 | for (let i = 0; i <= n; i++) { 40 | xorForRange ^= i; 41 | } 42 | let xorForArray = 0; 43 | for (let i = 0; i < n; i++) { 44 | xorForArray ^= nums[i]; 45 | } 46 | return xorForArray ^ xorForRange; 47 | } 48 | 49 | // Test 50 | console.log(findMissingNumber([4, 0, 3, 1])); // 2 51 | console.log(findMissingNumber([8, 3, 5, 2, 4, 6, 0, 1])); // 7 52 | console.log(findMissingNumber([3, 1, 2, 0])); // 4 53 | -------------------------------------------------------------------------------- /6_in-place-reversal-of-a-linked-list/206_reverse-linked-list.js: -------------------------------------------------------------------------------- 1 | const { printLinkedList, buildLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given the head of a Singly LinkedList, reverse the LinkedList. Write a function to 7 | * return the new head of the reversed LinkedList. 8 | * https://leetcode.com/problems/reverse-linked-list/ 9 | * 10 | * 11 | * Time: O(n) 12 | * Space: O(1) 13 | * 14 | * @param {ListNode} head 15 | * @return {ListNode} 16 | */ 17 | function reverseLinkedList(head) { 18 | let prev = null; 19 | let current = head; 20 | while (current) { 21 | const next = current.next; 22 | current.next = prev; 23 | prev = current; 24 | current = next; 25 | } 26 | return prev; 27 | } 28 | 29 | /** 30 | * Do the reversion in the recursive manner. 31 | */ 32 | function reverseLinkedListRecursively(head) { 33 | // set the stop condition first 34 | if (!head || !head.next) { 35 | return head; 36 | } 37 | // reverse from next to the end, return the end 38 | const end = reverseLinkedListRecursively(head.next); 39 | /** 40 | * Use example to illustrate: 1 -> 2 -> 3 -> 4 -> 5 -> null, at first head = 1, 41 | * after we do reverse from 2 to null, it will be 5 -> 4 -> 3 -> 2 -> null, but 42 | * 1 -> 2 is still there, so `head.next` is still 2, we need to reverse 1 -> 2 43 | * to 2 -> 1, so `head.next.next = head`, at the same time we need to do 1 -> null, 44 | * so `head.next = null` 45 | */ 46 | head.next.next = head; 47 | head.next = null; 48 | return end; 49 | } 50 | 51 | // Test 52 | 53 | // loop 54 | const head1 = buildLinkedList([2, 4, 6, 8, 10]); 55 | const newHead1 = reverseLinkedList(head1); 56 | printLinkedList(newHead1); // 10 -> 8 -> 6 -> 4 -> 2 57 | 58 | // recursion 59 | const head2 = new buildLinkedList([1, 3, 5, 7, 9]); 60 | const newHead2 = reverseLinkedListRecursively(head2); 61 | printLinkedList(newHead2); // 9 -> 7 -> 5 -> 3 -> 1 62 | -------------------------------------------------------------------------------- /5_cyclic-sort/442_find-all-duplicates-in-an-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * We are given an unsorted array containing n numbers taken from the range 1 to n. 5 | * The array has some numbers appearing twice, find all these duplicate numbers 6 | * without using any extra space. 7 | * https://leetcode.com/problems/find-all-duplicates-in-an-array/ 8 | * 9 | * Example 1: 10 | * Input: [3, 4, 4, 5, 5] 11 | * Output: [4, 5] 12 | * 13 | * Example 2: 14 | * Input: [5, 4, 7, 2, 3, 5, 3] 15 | * Output: [3, 5] 16 | * 17 | * Time: O(n) 18 | * Space: O(1) 19 | * 20 | * @param {number[]} nums 21 | * @return {number[]} 22 | */ 23 | function findAllDuplicateNumbers(nums) { 24 | for (let i = 0; i < nums.length; i++) { 25 | /** 26 | * Similar as 287_find-the-duplicate-number, instead of returning the number 27 | * directly when the two indices being swapped have the same value, here we 28 | * push the number into a result array. Note: we can't push the number to the 29 | * result array in the while loop (e.g. push to result when same value is 30 | * encountered before swap), cause that might push the same number multiple 31 | * times, think about this example: [4,3,2,7,8,2,3,1], the result will be 32 | * [3,3,2] if we do push in the while loop. 33 | */ 34 | while (nums[i] !== i + 1 && nums[nums[i] - 1] !== nums[i]) { 35 | _swap(nums, i, nums[i] - 1); 36 | } 37 | } 38 | const result = []; 39 | // doing this in a separate loop to prevent duplication in the result array 40 | for (let i = 0; i < nums.length; i++) { 41 | if (nums[i] !== i + 1) { 42 | result.push(nums[i]); 43 | } 44 | } 45 | return result; 46 | } 47 | 48 | function _swap(array, i, j) { 49 | [array[i], array[j]] = [array[j], array[i]]; 50 | } 51 | 52 | // Test 53 | console.log(findAllDuplicateNumbers([3, 4, 4, 5, 5])); // [4, 5] 54 | console.log(findAllDuplicateNumbers([5, 4, 7, 2, 3, 5, 3])); // [3, 5] 55 | -------------------------------------------------------------------------------- /1_sliding-window/0_smallest-subarray-with-given-sum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of positive numbers and a positive number ‘S’, find the length 5 | * of the smallest contiguous subarray whose sum is greater than or equal to ‘S’. 6 | * Return 0, if no such subarray exists. 7 | * 8 | * Example 1: 9 | * Input: [2, 1, 5, 2, 3, 2], S=7 10 | * Output: 2 11 | * Explanation: The smallest subarray with a sum great than or equal to '7' is [5, 2]. 12 | * 13 | * Example 2: 14 | * Input: [2, 1, 5, 2, 8], S=7 15 | * Output: 1 16 | * Explanation: The smallest subarray with a sum greater than or equal to '7' is [8]. 17 | * 18 | * Time: O(n) <- O(n + n) 19 | * Space: O(1) 20 | * 21 | * @param {number[]} array 22 | * @param {number} targetSum 23 | * @return {number} 24 | */ 25 | function smallestSubarrayWithGiveSum(array, targetSum) { 26 | let windowStart = 0; 27 | let windowEnd = 0; 28 | 29 | let minWindowCount = array.length + 1; 30 | let windowSum = 0; 31 | 32 | // O(n) -> `windowEnd` run through all indices 33 | while (windowEnd < array.length) { 34 | // Aggregation is the sum of the window 35 | windowSum += array[windowEnd]; 36 | windowEnd++; 37 | 38 | /** 39 | * Keep shrinking the window if the sum is larger than `targetSum`. 40 | * 41 | * Note: 42 | * * `windowCount = windowEnd - windowStart` because `windowEnd` is not in window yet 43 | * * O(n) in total -> `windowStart` run through all indices for all the outer loops 44 | */ 45 | while (windowSum >= targetSum) { 46 | minWindowCount = Math.min(minWindowCount, windowEnd - windowStart); 47 | windowSum -= array[windowStart]; 48 | windowStart++; 49 | } 50 | } 51 | 52 | return minWindowCount === array.length + 1 ? 0 : minWindowCount; 53 | } 54 | 55 | // Test 56 | console.log(smallestSubarrayWithGiveSum([2, 1, 5, 2, 3, 2], 7)); // 2 57 | console.log(smallestSubarrayWithGiveSum([2, 1, 5, 2, 8], 7)); // 1 58 | -------------------------------------------------------------------------------- /12_bitwise-xor/1009_complement-of-base-10-integer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Every non-negative integer N has a binary representation, for example, 8 can be 5 | * represented as "1000" in binary and 7 as "0111" in binary. The complement of a binary 6 | * representation is the number in binary that we get when we change every 1 to a 0 and 7 | * every 0 to a 1. For example, the binary complement of "1010" is "0101". For a given 8 | * positive number N in base-10, return the complement of its binary representation as a 9 | * base-10 integer. 10 | * https://leetcode.com/problems/complement-of-base-10-integer/ 11 | * 12 | * Example 1: 13 | * Input: 8 14 | * Output: 7 15 | * Explanation: 8 is 1000 in binary, its complement is 0111 in binary, which is 7 in 16 | * base-10. 17 | * 18 | * Example 2: 19 | * Input: 10 20 | * Output: 5 21 | * Explanation: 10 is 1010 in binary, its complement is 0101 in binary, which is 5 in 22 | * base-10. 23 | * 24 | * 25 | * Time: O(b) 26 | * Space: O(1) 27 | * 28 | * @param {number} num 29 | * @return {number} 30 | */ 31 | function findComplementOfBase10Integer(num) { 32 | /** 33 | * The main idea here is to find the full 1 digit in all the bits of binary 34 | * representation of `num`, and then use XOR to get the complement, because XOR 35 | * has this property: `num ^ complement = 1111...` => `num ^ 1111... = complement`. 36 | * So the problem become how to get the bit count of the original `num`. We can simply 37 | * use the while loop here to move the 1 left forward by one bit each time. 38 | */ 39 | let bitCount = 0; 40 | // this is exactly the same as `Math.pow(2, bitCount) - 1` 41 | while ((1 << bitCount) - 1 < num) { 42 | bitCount++; 43 | } 44 | // `(1 << bitCount) - 1)` is the full 1 digit binary 45 | return ((1 << bitCount) - 1) ^ num; 46 | } 47 | 48 | // Test 49 | console.log(findComplementOfBase10Integer(8)); // 7 50 | console.log(findComplementOfBase10Integer(10)); // 5 51 | -------------------------------------------------------------------------------- /6_in-place-reversal-of-a-linked-list/92_reverse-linked-list-ii.js: -------------------------------------------------------------------------------- 1 | const { printLinkedList, buildLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given the head of a LinkedList and two positions "p" and "q" (1-based), reverse the 7 | * LinkedList from position "p" to "q". 8 | * https://leetcode.com/problems/reverse-linked-list-ii/ 9 | * 10 | * 11 | * Time: O(q) 12 | * Space: O(1) 13 | * 14 | * @param {ListNode} head 15 | * @param {number} p 16 | * @param {number} q 17 | * @return {ListNode} 18 | */ 19 | function reverseSubLinkedList(head, p, q) { 20 | /** 21 | * Corner case check, if p === q, no need to do anything. 22 | */ 23 | if (p === q) { 24 | return head; 25 | } 26 | 27 | let i = 1; 28 | let current = head; 29 | let prev = null; 30 | // O(p) 31 | while (i < p) { 32 | // why `<`? use real example to help understand 33 | prev = current; 34 | current = current.next; 35 | i++; 36 | } 37 | // store the node before p because we need to modify its next pointer after reversal 38 | const copyOfNodeBeforeP = prev; 39 | const copyOfNodeP = current; 40 | // O(q - p) 41 | while (i <= q) { 42 | // why `<=`? use real example to help understand 43 | const next = current.next; 44 | current.next = prev; 45 | prev = current; 46 | current = next; 47 | i++; 48 | } 49 | // `current` is the node after q 50 | copyOfNodeP.next = current; 51 | /** 52 | * Corner case check: if the node before p is not existed, which means p is the 53 | * head node, we can return `prev` (q) directly, if it's existed, we need to modify 54 | * its next pointer to point to `prev` (q). 55 | */ 56 | if (copyOfNodeBeforeP) { 57 | copyOfNodeBeforeP.next = prev; 58 | return head; 59 | } 60 | return prev; 61 | } 62 | 63 | // Test 64 | const head = buildLinkedList([1, 2, 3, 4, 5]); 65 | const newHead = reverseSubLinkedList(head, 2, 4); 66 | printLinkedList(newHead); // 1 -> 4 -> 3 -> 2 -> 5 67 | -------------------------------------------------------------------------------- /1_sliding-window/1004_max-consecutive-ones-iii.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array containing 0s and 1s, if you are allowed to replace no more than ‘k’ 0s 5 | * with 1s, find the length of the longest contiguous subarray having all 1s. 6 | * https://leetcode.com/problems/max-consecutive-ones-iii/ 7 | * 8 | * Example 1: 9 | * Input: Array=[0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1], k=2 10 | * Output: 6 11 | * Explanation: Replace the '0' at index 5 and 8 to have the longest contiguous subarray of 12 | * 1s having length 6. 13 | * 14 | * Example 2: 15 | * Input: Array=[0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1], k=3 16 | * Output: 9 17 | * Explanation: Replace the '0' at index 6, 9, and 10 to have the longest contiguous subarray 18 | * of 1s having length 9. 19 | * 20 | * Time: O(n) 21 | * Space: O(1) 22 | * 23 | * Note: exactly the same as 424_longest-repeating-character-replacement.js, see detail 24 | * explanations there, the only difference is for this problem we only care the count of 25 | * the "1" (no charCountMap required). 26 | * 27 | * @param {number[]} array 28 | * @param {number} k 29 | * @return {number} 30 | */ 31 | function maxConsecutiveOnesIII(array, k) { 32 | let windowStart = 0; 33 | let windowEnd = 0; 34 | 35 | let oneCountInWindow = 0; 36 | let historicalMaxOneCount = 0; 37 | 38 | while (windowEnd < array.length) { 39 | if (array[windowEnd] === 1) { 40 | oneCountInWindow++; 41 | } 42 | historicalMaxOneCount = Math.max(historicalMaxOneCount, oneCountInWindow); 43 | windowEnd++; 44 | 45 | if (historicalMaxOneCount + k < windowEnd - windowStart) { 46 | if (array[windowStart] === 1) { 47 | oneCountInWindow--; 48 | } 49 | windowStart++; 50 | } 51 | } 52 | 53 | return array.length - windowStart; 54 | } 55 | 56 | // Test 57 | console.log(maxConsecutiveOnesIII([0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1], 2)); // 6 58 | console.log(maxConsecutiveOnesIII([0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1], 3)); // 9 59 | -------------------------------------------------------------------------------- /5_cyclic-sort/0_find-the-corrupt-pair.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * We are given an unsorted array containing n numbers taken from the range 1 to n. 5 | * The array originally contained all the numbers from 1 to n, but due to a data 6 | * error, one of the numbers got duplicated which also resulted in one number going 7 | * missing. Find both these numbers. 8 | * 9 | * Example 1: 10 | * Input: [3, 1, 2, 5, 2] 11 | * Output: [2, 4] 12 | * Explanation: '2' is duplicated and '4' is missing. 13 | * 14 | * Example 2: 15 | * Input: [3, 1, 2, 3, 6, 4] 16 | * Output: [3, 5] 17 | * Explanation: '3' is duplicated and '5' is missing. 18 | * 19 | * Time: O(n) 20 | * Space: O(1) 21 | * 22 | * 23 | * @param {number[]} nums 24 | * @return {number[]} 25 | */ 26 | function findCorruptPair(nums) { 27 | /** 28 | * Similar as 287_find-the-duplicate-number, the difference is after finding 29 | * the number, we can't stop the loop, we need to make sure all the other numbers 30 | * are being swapped to their correct positions in order to find the missing 31 | * number below. 32 | * 33 | */ 34 | for (let i = 0; i < nums.length; i++) { 35 | while (nums[i] !== i + 1) { 36 | if (nums[i] !== nums[nums[i] - 1]) { 37 | _swap(nums, i, nums[i] - 1); 38 | } else { 39 | break; 40 | } 41 | } 42 | } 43 | /** 44 | * Find the missing number: after finding the duplicate number, we just need to 45 | * go through the array and find the number which is not on its correct position, 46 | * the number itself is the duplicate number, and the position itself can derive 47 | * the missing number; 48 | */ 49 | for (let i = 0; i < nums.length; i++) { 50 | if (nums[i] !== i + 1) { 51 | return [nums[i], i + 1]; 52 | } 53 | } 54 | } 55 | 56 | function _swap(array, i, j) { 57 | [array[i], array[j]] = [array[j], array[i]]; 58 | } 59 | 60 | // Test 61 | console.log(findCorruptPair([3, 1, 2, 5, 2])); // [2, 4] 62 | console.log(findCorruptPair([3, 1, 2, 3, 6, 4])); // [3, 5] 63 | -------------------------------------------------------------------------------- /10_subsets/22_generate-parentheses.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * For a given number N, write a function to generate all combination of ‘N’ pairs 5 | * of balanced parentheses. 6 | * https://leetcode.com/problems/generate-parentheses/ 7 | * 8 | * Example 1: 9 | * Input: N=2 10 | * Output: (()), ()() 11 | * 12 | * Example 2: 13 | * Input: N=3 14 | * Output: ((())), (()()), (())(), ()(()), ()()() 15 | * 16 | * 17 | * Time: O(n 2^n) 18 | * Space: O(n 2^n) <- result 19 | * 20 | * @param {number} num 21 | * @return {string[]} 22 | */ 23 | function generateParentheses(num) { 24 | const parentheses = []; 25 | _backtrack(num, ['('], 1, 0, parentheses); 26 | return parentheses; 27 | } 28 | 29 | function _backtrack(num, curList, openCount, closeCount, result) { 30 | if (curList.length === 2 * num) { 31 | // O(n) to join array to string 32 | result.push(curList.join('')); 33 | return; 34 | } 35 | /** 36 | * When we construct the `choices` array (see README), we need to consider 37 | * the valid options based on the current `openCount` (count of `(`) and 38 | * `closeCount` (count of `)`): 39 | * 1. if `openCount < num`, we can still append `(`, because the maximum 40 | * `(` can not exceed `num`. 41 | * 2. if `closeCount < openCount`, we can still append `)`, because the 42 | * maximum `)` in any time can't exceed `(`, otherwise it won't be valid 43 | * 44 | * Rough time estimation: every position we have 2 choices, so it would be 45 | * 2^(2n) -> O(2^n) 46 | */ 47 | if (openCount < num) { 48 | curList.push('('); 49 | _backtrack(num, curList, openCount + 1, closeCount, result); 50 | curList.pop(); 51 | } 52 | if (closeCount < openCount) { 53 | curList.push(')'); 54 | _backtrack(num, curList, openCount, closeCount + 1, result); 55 | curList.pop(); 56 | } 57 | } 58 | 59 | // Test 60 | console.log(generateParentheses(2)); 61 | // [ '(())', '()()' ] 62 | console.log(generateParentheses(3)); 63 | // [ '((()))', '(()())', '(())()', '()(())', '()()()' ] 64 | -------------------------------------------------------------------------------- /6_in-place-reversal-of-a-linked-list/0_reverse-alternating-k-group.js: -------------------------------------------------------------------------------- 1 | const { buildLinkedList, printLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given the head of a LinkedList and a number k, reverse every alternating k 7 | * sized sub-list starting from the head. If, in the end, you are left with a 8 | * sub-list with less than k elements, reverse it too. 9 | * 10 | * Time: O(n) 11 | * Space: O(1) 12 | * 13 | * @param {ListNode} head 14 | * @param {number} k 15 | * @return {ListNode} 16 | */ 17 | function reverseAlternateKNodes(head, k) { 18 | let newHead; 19 | let current = head; 20 | let prev = null; 21 | while (current) { 22 | const endOfLastGroupAfterReversal = prev; 23 | const startOfCurrentGroup = current; 24 | let nodeCount = 1; 25 | while (current && nodeCount <= k) { 26 | const next = current.next; 27 | current.next = prev; 28 | prev = current; 29 | current = next; 30 | nodeCount++; 31 | } 32 | startOfCurrentGroup.next = current; 33 | if (!endOfLastGroupAfterReversal) { 34 | newHead = prev; 35 | } else { 36 | endOfLastGroupAfterReversal.next = prev; 37 | } 38 | 39 | /** 40 | * The above section is exactly the same as 25_reverse-nodes-in-k-group, 41 | * the only difference is we don't need to update `prev` because that will 42 | * be updated below: the next k nodes doesn't need to be reversed. 43 | */ 44 | while (current && nodeCount <= 2 * k) { 45 | prev = current; 46 | current = current.next; 47 | nodeCount++; 48 | } 49 | } 50 | return newHead; 51 | } 52 | 53 | // Test 54 | const head1 = buildLinkedList([1, 2, 3, 4, 5, 6, 7, 8]); 55 | const newHead1 = reverseAlternateKNodes(head1, 3); 56 | printLinkedList(newHead1); // 3 -> 2 -> 1 -> 4 -> 5 -> 6 -> 8 -> 7 57 | 58 | const head2 = buildLinkedList([1, 2, 3, 4, 5, 6, 7, 8, 9]); 59 | const newHead2 = reverseAlternateKNodes(head2, 2); 60 | printLinkedList(newHead2); // 2 -> 1 -> 3 -> 4 -> 6 -> 5 -> 7 -> 8 -> 9 61 | -------------------------------------------------------------------------------- /8_DFS/1430_check-if-a-string-is-a-valid-sequence-from-root-to-leaves-path-in-a-binary-tree.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree and a number sequence, find if the sequence is present as a 7 | * root-to-leaf path in the given tree. 8 | * https://leetcode.com/problems/check-if-a-string-is-a-valid-sequence-from-root-to-leaves-path-in-a-binary-tree/ 9 | * 10 | * Example 1: 11 | * Input: [1, 7, 9, null, null, 2, 9], [1, 9, 9] 12 | * Output: true 13 | * 14 | * Example 2: 15 | * Input: [1, 0, 1, null, 1, 6, 5], [1, 0, 7] 16 | * Output: false 17 | * 18 | * 19 | * Time: O(n) 20 | * Space: O(h) <- recursion stack 21 | * 22 | * @param {TreeNode} root 23 | * @param {number[]} sequence 24 | * @return {boolean} 25 | */ 26 | function hasPathWithGiveSequence(root, sequence) { 27 | if (!root) { 28 | return sequence.length === 0; 29 | } 30 | return _traverse(root, sequence, 0); 31 | } 32 | 33 | function _traverse(node, sequence, curIndex) { 34 | /** 35 | * Similar as 112_path-sum, but here we need to check more invalid conditions: 36 | * * `curIndex` can't be larger than the `sequence` length 37 | * * `curIndex` of sequence must be equal to the current node value 38 | */ 39 | if (!node || curIndex >= sequence.length || sequence[curIndex] !== node.val) { 40 | return false; 41 | } 42 | if (!node.left && !node.right) { 43 | /** 44 | * When we reach the leaf node, the sequence must also be ended, otherwise 45 | * it's also invalid. 46 | */ 47 | return curIndex === sequence.length - 1; 48 | } 49 | 50 | return ( 51 | _traverse(node.left, sequence, curIndex + 1) || 52 | _traverse(node.right, sequence, curIndex + 1) 53 | ); 54 | } 55 | 56 | // Test 57 | const tree1 = buildTreeBFS([1, 7, 9, null, null, 2, 9]); 58 | console.log(hasPathWithGiveSequence(tree1, [1, 9, 9])); // true 59 | 60 | const tree2 = buildTreeBFS([1, 0, 1, null, 1, 6, 5]); 61 | console.log(hasPathWithGiveSequence(tree2, [1, 0, 7])); // false 62 | -------------------------------------------------------------------------------- /10_subsets/784_letter-case-permutation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a string, find all of its permutations preserving the character sequence but 5 | * changing case. 6 | * https://leetcode.com/problems/letter-case-permutation/ 7 | * 8 | * Example 1: 9 | * Input: "ad52" 10 | * Output: "ad52", "Ad52", "aD52", "AD52" 11 | * 12 | * Example 2: 13 | * Input: "ab7c" 14 | * Output: "ab7c", "Ab7c", "aB7c", "AB7c", "ab7C", "Ab7C", "aB7C", "AB7C" 15 | * 16 | * 17 | * Time: O(n 2^n) 18 | * Space: O(n 2^n) <- result 19 | * 20 | * @param {string} str 21 | * @return {string[]} 22 | */ 23 | function findLetterCaseStringPermutation(str) { 24 | const permutations = []; 25 | _backtrack(str, 0, [], permutations); 26 | return permutations; 27 | } 28 | 29 | function _backtrack(str, curIndex, curList, result) { 30 | if (curList.length === str.length) { 31 | // O(n) to join array to a string 32 | result.push(curList.join('')); 33 | return; 34 | } 35 | 36 | const curChar = str[curIndex]; 37 | /** 38 | * Still the same backtrack approach here, the difference is here for each index, 39 | * we only have 2 choices at most: lowercase and uppercase, for non-string index, 40 | * there is only one choice. So we construct the choices array first here and loop 41 | * through it. 42 | * 43 | * Time: O(2^n) cause for each index, there's at most 2 choices. 44 | */ 45 | let choices = [curChar]; 46 | if (!_isNumber(curChar)) { 47 | choices = [curChar.toLowerCase(), curChar.toUpperCase()]; 48 | } 49 | for (let i = 0; i < choices.length; i++) { 50 | curList.push(choices[i]); 51 | _backtrack(str, curIndex + 1, curList, result); 52 | curList.pop(); 53 | } 54 | } 55 | 56 | function _isNumber(char) { 57 | return char >= '0' && char <= '9'; 58 | } 59 | 60 | // Test 61 | console.log(findLetterCaseStringPermutation('ad52')); 62 | // ['ad52', 'Ad52', 'aD52', 'AD52'] 63 | console.log(findLetterCaseStringPermutation('ab7c')); 64 | // ['ab7c', 'Ab7c', 'aB7c', 'AB7c', 'ab7C', 'Ab7C', 'aB7C', 'AB7C'] 65 | -------------------------------------------------------------------------------- /13_top-k-elements/451_sort-characters-by-frequency.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a string, sort it based on the decreasing frequency of its characters. 5 | * https://leetcode.com/problems/sort-characters-by-frequency/ 6 | * 7 | * Example 1: 8 | * Input: "Programming" 9 | * Output: "rrggmmPiano" 10 | * Explanation: 'r', 'g', and 'm' appeared twice, so they need to appear before any 11 | * other character. 12 | * 13 | * Example 2: 14 | * Input: "abcbab" 15 | * Output: "bbbaac" 16 | * Explanation: 'b' appeared three times, 'a' appeared twice, and 'c' appeared only once. 17 | * 18 | * Note: this problem is similar as 347_top-k-frequent-elements, we can treat this 19 | * problem as "top n frequent elements" and use the same strategy to solve it. However, 20 | * we will try to use a different solution here to demonstrate how "counting sort" is 21 | * used when solving problems related to "frequency". 22 | * 23 | * Time: O(n) 24 | * Space: O(n) 25 | * 26 | * @param {string} str 27 | * @return {string} 28 | */ 29 | function sortCharactersByFrequency(str) { 30 | const freqMap = {}; 31 | // O(n) 32 | for (let i = 0; i < str.length; i++) { 33 | freqMap[str[i]] = (freqMap[str[i]] || 0) + 1; 34 | } 35 | const buckets = new Array(str.length + 1); 36 | // O(m): m is the unique character count 37 | Object.keys(freqMap).forEach((char) => { 38 | if (!buckets[freqMap[char]]) { 39 | buckets[freqMap[char]] = []; 40 | } 41 | buckets[freqMap[char]].push(char); 42 | }); 43 | const result = []; 44 | /** 45 | * loop inside loop, but the total time complexity is O(n), cause each character 46 | * will only be pushed once. 47 | */ 48 | for (let i = str.length; i > 0; i--) { 49 | if (buckets[i]) { 50 | buckets[i].forEach((char) => { 51 | result.push(char.repeat(i)); 52 | }); 53 | } 54 | } 55 | return result.join(''); 56 | } 57 | 58 | // Test 59 | console.log(sortCharactersByFrequency('Programming')); // 'rrggmmPoain' 60 | console.log(sortCharactersByFrequency('abcbab')); // 'bbbaac' 61 | -------------------------------------------------------------------------------- /7_BFS/111_minimum-depth-of-binary-tree.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Find the minimum depth of a binary tree. The minimum depth is the number of nodes 7 | * along the shortest path from the root node to the nearest leaf node. 8 | * https://leetcode.com/problems/minimum-depth-of-binary-tree/ 9 | * 10 | * Example 1: 11 | * Input: [1, 2, 3, 4, 5] 12 | * Output: 2 13 | * 14 | * Example 2: 15 | * Input: [12, 7, 1, null, 9, 10, 5, null, null, 11] 16 | * Output: 3 17 | * 18 | * 19 | * Time: O(n) 20 | * Space: O(n) 21 | * 22 | * @param {TreeNode} root 23 | * @return {number} 24 | */ 25 | function minimumTreeDepth(root) { 26 | if (!root) { 27 | return 0; 28 | } 29 | 30 | const levelNodes = [root]; 31 | let curDepth = 1; 32 | while (levelNodes.length > 0) { 33 | const levelLength = levelNodes.length; 34 | for (let i = 0; i < levelLength; i++) { 35 | const node = levelNodes.shift(); 36 | /** 37 | * The key is to find the 1st leaf node in the level order traversal, 38 | * the height for this leaf node is guaranteed to be the minimal depth. 39 | * The reason is BFS do the traversal level by level, as long as a leaf 40 | * node is reached, it is the smallest depth. That's the benefit of BFS, 41 | * if we use DFS here, we need to reach each path from the root to each 42 | * leaf and get all the depths, then compare them to find the minimum 43 | * one, that's way less effective. 44 | */ 45 | if (!node.left && !node.right) { 46 | return curDepth; 47 | } 48 | if (node.left) { 49 | levelNodes.push(node.left); 50 | } 51 | if (node.right) { 52 | levelNodes.push(node.right); 53 | } 54 | } 55 | curDepth++; 56 | } 57 | } 58 | 59 | // Test 60 | const tree1 = buildTreeBFS([1, 2, 3, 4, 5]); 61 | console.log(minimumTreeDepth(tree1)); // 2 62 | 63 | const tree2 = buildTreeBFS([12, 7, 1, null, 9, 10, 5, null, null, 11]); 64 | console.log(minimumTreeDepth(tree2)); // 3 65 | -------------------------------------------------------------------------------- /11_binary-search/153_find-minimum-in-rotated-sorted-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of numbers which is sorted in ascending order and also rotated by some 5 | * arbitrary number, find the minimum number in the array. You can assume that the given 6 | * array does not have any duplicates. 7 | * https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/ 8 | * 9 | * Example 1: 10 | * Input: [10, 15, 1, 3, 8] 11 | * Output: 1 12 | * 13 | * Example 2: 14 | * Input: [4, 5, 7, 9, 10, -1, 2] 15 | * Output: -1 16 | * 17 | * 18 | * Time: 19 | * Space: 20 | * 21 | * @param {number[]} nums 22 | * @return {number} 23 | */ 24 | function findMinimumInRotatedArray(nums) { 25 | let start = 0; 26 | // we need to use `nums.length - 1` because we access `nums[end]` in the loop 27 | let end = nums.length - 1; 28 | // we need to sue `start < end` because we use `end = middle` in the loop 29 | while (start < end) { 30 | const middle = start + Math.floor((end - start) / 2); 31 | if (nums[middle] > nums[end]) { 32 | /** 33 | * [middle, end] is not sorted, so the minimum number must fall in the right part. 34 | * Why `middle + 1` here? Because `nums[middle] > nums[end]`, so `middle` is not 35 | * possible to be the minimum (at least it's larger than `end`). 36 | */ 37 | start = middle + 1; 38 | } else { 39 | /** 40 | * Why `end = middle` here? Because when `nums[middle] <= nums[end]`, this part 41 | * is sorted, so `middle` is still a possible candidate for the minimum number. 42 | */ 43 | end = middle; 44 | } 45 | } 46 | /** 47 | * Can the final value of `start` out of range (i.e. `start === nums.length`)? It's 48 | * not possible here cause the initial value of `end` is `nums.length - 1` and the loop 49 | * condition is `while (start < end)`. 50 | */ 51 | return nums[start]; 52 | } 53 | 54 | // Test 55 | console.log(findMinimumInRotatedArray([10, 15, 1, 3, 8])); // 1 56 | console.log(findMinimumInRotatedArray([4, 5, 7, 9, 10, -1, 2])); // -1 57 | -------------------------------------------------------------------------------- /13_top-k-elements/0_sum-of-elements-between-k1-and-k2.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given an array, find the sum of all numbers between the K1'th and K2'th smallest 7 | * elements (not inclusive) of that array. 8 | * 9 | * Example 1: 10 | * Input: [1, 3, 12, 5, 15, 11], and K1=3, K2=6 11 | * Output: 23 12 | * Explanation: The 3rd smallest number is 5 and 6th smallest number 15. The sum of 13 | * numbers coming between 5 and 15 is 23 (11+12). 14 | * 15 | * Example 2: 16 | * Input: [3, 5, 8, 7], and K1=1, K2=4 17 | * Output: 12 18 | * Explanation: The sum of the numbers between the 1st smallest number (3) and the 4th 19 | * smallest number (8) is 12 (5+7). 20 | * 21 | * 22 | * Time: O((n + k2 - k1) log(k2)) 23 | * Space: O(k2) 24 | * 25 | * @param {number[]} nums 26 | * @param {number} k1 27 | * @param {number} k2 28 | * @return {number} 29 | */ 30 | function sumOfElementsBetweenK1AndK2(nums, k1, k2) { 31 | /** 32 | * Very similar as the problem 0_kth-smallest-element-in-an-array, here we need to 33 | * find the smallest k2 numbers in the array, so follow the same approach, use 34 | * maximum heap we can get the smallest k2 numbers in the heap, pop() `k2 - k1 -1` 35 | * numbers and then we can get the final sum. 36 | * 37 | * Note: the naive solution would be sort the array and do the sum calculation, which 38 | * will cost O(n log(n) + (k2 - k1)), which is slower than this approach. 39 | */ 40 | const maxHeap = new Heap((a, b) => a - b); 41 | // O(n log(k2)) 42 | for (let i = 0; i < nums.length; i++) { 43 | if (i < k2 - 1) { 44 | maxHeap.push(nums[i]); 45 | } else if (nums[i] < maxHeap.peek()) { 46 | maxHeap.pop(); 47 | maxHeap.push(nums[i]); 48 | } 49 | } 50 | let sum = 0; 51 | // O((k2 - k1) log(k2)) 52 | while (k2 - 1 > k1) { 53 | sum += maxHeap.pop(); 54 | k2--; 55 | } 56 | return sum; 57 | } 58 | 59 | // Test 60 | console.log(sumOfElementsBetweenK1AndK2([1, 3, 12, 5, 15, 11], 3, 6)); // 23 61 | console.log(sumOfElementsBetweenK1AndK2([3, 5, 8, 7], 1, 4)); // 12 62 | -------------------------------------------------------------------------------- /10_subsets/46_permutations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a set of distinct numbers, find all of its permutations. Permutation is 5 | * defined as the re-arranging of the elements of the set. For example, {1, 2, 3} has 6 | * the following six permutations: 7 | * {1, 2, 3} 8 | * {1, 3, 2} 9 | * {2, 1, 3} 10 | * {2, 3, 1} 11 | * {3, 1, 2} 12 | * {3, 2, 1} 13 | * If a set has ‘n’ distinct elements it will have n! permutations. 14 | * 15 | * Example 1: 16 | * Input: [1,3,5] 17 | * Output: [1,3,5], [1,5,3], [3,1,5], [3,5,1], [5,1,3], [5,3,1] 18 | * 19 | * 20 | * Time: O(n n!) 21 | * Space: O(n n!) <- result 22 | * 23 | * @param {number[]} nums 24 | * @return {number[][]} 25 | */ 26 | function findPermutations(nums) { 27 | const permutations = []; 28 | const used = new Array(nums.length).fill(false); 29 | _backtrack(nums, [], permutations, used); 30 | return permutations; 31 | } 32 | 33 | function _backtrack(nums, curList, result, used) { 34 | if (curList.length === nums.length) { 35 | // O(n) to copy an array 36 | result.push(Array.from(curList)); 37 | } else { 38 | /** 39 | * Similar as 78_subsets, the difference is when we reach the leaf node 40 | * (say [1, 2, 3]) and then go up to [1] -> [1, 3] (check explanation of 41 | * 78_subsets), we need to go backward to visit 2 again to get [1, 3, 2]. 42 | * That's why we make use of a `used` array to maintain which number is 43 | * used in the recursion. 44 | */ 45 | for (let i = 0; i < nums.length; i++) { 46 | if (used[i]) { 47 | continue; 48 | } 49 | curList.push(nums[i]); 50 | used[i] = true; 51 | _backtrack(nums, curList, result, used); 52 | curList.pop(); 53 | used[i] = false; 54 | } 55 | } 56 | } 57 | 58 | // Test 59 | const result1 = findPermutations([1, 2, 3]); 60 | result1.forEach((i) => console.log(i)); 61 | // [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1] 62 | 63 | const result2 = findPermutations([1, 3, 5]); 64 | result2.forEach((i) => console.log(i)); 65 | // [[1, 3, 5], [1, 5, 3], [3, 1, 5], [3, 5, 1], [5, 1, 3], [5, 3, 1]] 66 | -------------------------------------------------------------------------------- /10_subsets/README.md: -------------------------------------------------------------------------------- 1 | # Subsets 2 | 3 | ## When to use 4 | 5 | A huge number of coding interview problems involve dealing with Permutations and Combinations of a given set of elements. This pattern describes an efficient backtracking approach to handle all these problems. 6 | 7 | ## Pseudo code 8 | 9 | ```javascript 10 | function backtrack(startIndex) { 11 | if (shouldEnd()) { 12 | handleResult(); 13 | return; 14 | } 15 | 16 | for (let i = startIndex; i < choices.length; i++) { 17 | doChoice(i); 18 | used[i] = true; 19 | backtrack(i + 1); 20 | revertChoice(i); 21 | used[i] = false; 22 | } 23 | } 24 | ``` 25 | 26 | These questions are all related to choices, we can think about the process as a decision tree (check explanation in [78_subsets](./78_subsets.js)), every step we have some choices to choose, we go downwards first to choose one choice each step, after reaching the leaf (all choices are chosen at one branch), then we go upward again to choose other choices. 27 | 28 | The key difference between the subset/permutation/combination problems is the loop variable `i` above, take `[1, 2, 3]` as input array: 29 | 30 | - `subset`: the final result is `[ [], [1], [1, 2], [1, 2, 3], [2], [2, 3], [3] ]`, `i` should start with `startIndex` because once 1 is processed (starting from 1), 1 won't be appeared again when we process 2, and every time we hit `backtrack` the result should be pushed to the final array. In addition, no `used` is required because `i` starts from `startIndex`. 31 | - `permutation`: the final result is `[ [1, 2, 3], [1, 3, 2], [2, 1, 3]. [2, 3, 1], [3, 1, 2], [3, 2, 1] ]`, `i` should always start from index 0, cause once 1 is processed, it can still be appeared when we process 2. `used` is required because `i` always starts from 0, we need to know if `i` is already used or not. In addition, result is only needed to be handled when it contains 3 numbers in it. 32 | - `combination`: the final result is `[ [1, 2], [2, 3], [1, 3] ]` if we are trying to find the combination of 2. This is pretty similar as `subsets`, the only difference is we need to handle result when it already contains 2 numbers in it. 33 | -------------------------------------------------------------------------------- /2_two-pointers/16_3sum-closest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of unsorted numbers and a target number, find a triplet in the array 5 | * whose sum is as close to the target number as possible, return the sum of the triplet. 6 | * If there are more than one such triplet, return the sum of the triplet with the 7 | * smallest sum. 8 | * https://leetcode.com/problems/3sum-closest/ 9 | * 10 | * Example 1: 11 | * Input: [-2, 0, 1, 2], target=2 12 | * Output: 1 13 | * Explanation: The triplet [-2, 1, 2] has the closest sum to the target. 14 | * 15 | * Example 2: 16 | * Input: [-3, -1, 1, 2], target=1 17 | * Output: 0 18 | * Explanation: The triplet [-3, 1, 2] has the closest sum to the target. 19 | * 20 | * Time: O(n^2) <- O(nlog(n)) + O(n^2) 21 | * Space: O(n) <- merge sort 22 | * 23 | * Note: similar approach as 15_3sum.js, check there for detailed explanation. 24 | * 25 | * @param {number[]} array 26 | * @param {number} targetSum 27 | * @return {number} 28 | */ 29 | function tripletSumCloseToTarget(array, targetSum) { 30 | // O(nlog(n)) 31 | array.sort((a, b) => a - b); 32 | 33 | let minSum = Infinity; 34 | 35 | // O(n) 36 | for (let i = 0; i <= array.length - 3; i++) { 37 | let left = i + 1; 38 | let right = array.length - 1; 39 | 40 | // O(n) 41 | while (left < right) { 42 | const sum = array[i] + array[left] + array[right]; 43 | if (sum === targetSum) { 44 | return targetSum; 45 | } 46 | if (sum < targetSum) { 47 | left++; 48 | } else { 49 | right--; 50 | } 51 | if ( 52 | // 1st condition: find the smallest diff 53 | Math.abs(targetSum - sum) < Math.abs(targetSum - minSum) || 54 | // 2nd condition: find the smaller one if the diff are the same 55 | (Math.abs(targetSum - sum) === Math.abs(targetSum - minSum) && 56 | sum < targetSum) 57 | ) { 58 | minSum = sum; 59 | } 60 | } 61 | } 62 | 63 | return minSum; 64 | } 65 | 66 | // Test 67 | console.log(tripletSumCloseToTarget([-2, 0, 1, 2], 2)); // 1 68 | console.log(tripletSumCloseToTarget([-3, -1, 1, 2], 1)); // 0 69 | -------------------------------------------------------------------------------- /11_binary-search/0_minimum-difference-element.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of numbers sorted in ascending order, find the element in the array 5 | * that has the minimum difference with the given key. 6 | * 7 | * Example 1: 8 | * Input: [4, 6, 10], key = 7 9 | * Output: 6 10 | * Explanation: The difference between the key '7' and '6' is minimum than any other 11 | * number in the array 12 | * 13 | * Example 2: 14 | * Input: [4, 6, 10], key = 4 15 | * Output: 4 16 | * 17 | * 18 | * Time: O(log n) 19 | * Space: O(1) 20 | * 21 | * @param {number[]} nums 22 | * @param {number[]} key 23 | * @return {number} 24 | */ 25 | function findMinimumDifferentElement(nums, key) { 26 | let start = 0; 27 | let end = nums.length; 28 | while (start < end) { 29 | const middle = start + Math.floor((end - start) / 2); 30 | if (nums[middle] >= key) { 31 | if (nums[middle] === key) { 32 | return key; 33 | } 34 | end = middle; 35 | } else { 36 | start = middle + 1; 37 | } 38 | } 39 | /** 40 | * Follow the traditional binary search method above, after getting `start`, we want 41 | * to check the following scenarios: 42 | * * if `start` is out of range (e.g. `key` is larger than the largest number 43 | * in array), then the last number is the answer 44 | * * if `key` is not existed, since `start` is the new inserted position, we need to 45 | * check its previous number and next number to see whose difference is smaller. 46 | * 47 | */ 48 | if (start === nums.length) { 49 | return nums[nums.length - 1]; 50 | } 51 | // if `start === 0` we treat the `prev` as -Infinity, so it won't be chosen. 52 | const prev = start > 0 ? nums[start - 1] : -Infinity; 53 | const next = nums[start]; 54 | if (key - prev < next - key) { 55 | return prev; 56 | } 57 | return next; 58 | } 59 | 60 | // Test 61 | console.log(findMinimumDifferentElement([4, 6, 10], 7)); // 6 62 | console.log(findMinimumDifferentElement([4, 6, 10], 4)); // 4 63 | console.log(findMinimumDifferentElement([4, 6, 10], 17)); // 10 64 | console.log(findMinimumDifferentElement([4, 6, 10], 3)); // 4 65 | console.log(findMinimumDifferentElement([1, 3, 8, 10, 15], 12)); // 10 66 | -------------------------------------------------------------------------------- /13_top-k-elements/1167_minimum-cost-to-connect-sticks.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given N sticks with different lengths, we need to connect these sticks into one big 7 | * rope with minimum cost. The cost of connecting two sticks is equal to the sum of 8 | * their lengths. 9 | * https://leetcode.com/problems/minimum-cost-to-connect-sticks/ (subscription) 10 | * 11 | * Example 1: 12 | * Input: [1, 3, 11, 5] 13 | * Output: 33 14 | * Explanation: First connect 1+3(=4), then 4+5(=9), and then 9+11(=20). So the total 15 | * cost is 33 (4+9+20) 16 | * 17 | * Example 2: 18 | * Input: [3, 4, 5, 6] 19 | * Output: 36 20 | * Explanation: First connect 3+4(=7), then 5+6(=11), 7+11(=18). Total cost is 21 | * 36 (7+11+18) 22 | * 23 | * 24 | * Time: O(n) + O(n log(n)) 25 | * Space: O(n) <- for heap 26 | * 27 | * @param {number[]} sticks 28 | * @return {number} 29 | */ 30 | function findMinCostToConnectSticks(sticks) { 31 | // O(n) for constructing heap in place 32 | const minHeap = new Heap((a, b) => b - a, sticks); 33 | let cost = 0; 34 | /** 35 | * To solve this problems, basically we need to always choose the 2 smallest 36 | * numbers in the array, and push the sum back to array, again to choose 2 smallest 37 | * numbers. 38 | * 39 | * Why use minimum heap here? Every time we get a sum of the two smallest numbers, 40 | * we need to compare the remaining numbers with this sum and get the 2 smallest 41 | * numbers. So minimum heap is the most efficient data structure for this purpose. 42 | */ 43 | while (minHeap.size() > 1) { 44 | /** 45 | * For each loop, we pop() 2 numbers and push() back 1 number, so basically the 46 | * loop with run N times for the whole array, the time complexity will be 47 | * O(n * 3 * log(n)) => O(n log(n)) 48 | */ 49 | // 2 x O(log(n)) 50 | const sum = minHeap.pop() + minHeap.pop(); 51 | cost += sum; 52 | if (minHeap.size() > 0) { 53 | // O(log(n)) 54 | minHeap.push(sum); 55 | } 56 | } 57 | return cost; 58 | } 59 | 60 | // Test 61 | console.log(findMinCostToConnectSticks([1, 3, 11, 5])); // 33 62 | console.log(findMinCostToConnectSticks([3, 4, 5, 6])); // 36 63 | -------------------------------------------------------------------------------- /13_top-k-elements/973_k-closest-points-to-origin.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given an array of points in the a 2D plane, find K closest points to the origin 7 | * (0, 0). Here, the distance between two points on a plane is the Euclidean distance. 8 | * https://leetcode.com/problems/k-closest-points-to-origin/ 9 | * 10 | * Example 1: 11 | * Input: points = [[1,2],[1,3]], K = 1 12 | * Output: [[1,2]] 13 | * Explanation: The Euclidean distance between (1, 2) and the origin is sqrt(5). The 14 | * Euclidean distance between (1, 3) and the origin is sqrt(10). Since sqrt(5) < sqrt(10) 15 | * therefore (1, 2) is closer to the origin. 16 | * 17 | * Example 2: 18 | * Input: point = [[1, 3], [3, 4], [2, -1]], K = 2 19 | * Output: [[1, 3], [2, -1]] 20 | * 21 | * 22 | * Time: O(n log(k)) 23 | * Space: O(k) <- for heap 24 | * 25 | * @param {number[][]} points 26 | * @param {number} k 27 | * @return {number[][]} 28 | */ 29 | function findKClosestPointsToOrigin(points, k) { 30 | /** 31 | * Similar as the problem 0_kth-smallest-element-in-an-array, the difference here 32 | * is we need to store the index in the heap together with the distance (comparison 33 | * factor). 34 | */ 35 | const maxHeap = new Heap((a, b) => a[1] - b[1]); 36 | for (let i = 0; i < points.length; i++) { 37 | const distance = _getDistance(points[i]); 38 | if (i < k) { 39 | // item in heap: [ index, distance ] 40 | maxHeap.push([i, distance]); 41 | } else if (distance < maxHeap.peek()[1]) { 42 | maxHeap.pop(); 43 | maxHeap.push([i, distance]); 44 | } 45 | } 46 | const array = maxHeap.toArray(); 47 | return array.map((item) => points[item[0]]); 48 | } 49 | 50 | function _getDistance(point) { 51 | return point[0] * point[0] + point[1] * point[1]; 52 | } 53 | 54 | // Test 55 | const result1 = findKClosestPointsToOrigin( 56 | [ 57 | [1, 2], 58 | [1, 3], 59 | ], 60 | 1 61 | ); 62 | result1.forEach((i) => console.log(i)); // [[1, 2]] 63 | 64 | const result2 = findKClosestPointsToOrigin( 65 | [ 66 | [1, 3], 67 | [3, 4], 68 | [2, -1], 69 | ], 70 | 2 71 | ); 72 | result2.forEach((i) => console.log(i)); // [[1, 3], [2, -1]] 73 | -------------------------------------------------------------------------------- /4_merge-intervals/0_maximum-cpu-load.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * We are given a list of Jobs. Each job has a Start time, an End time, and a CPU 7 | * load when it is running. Our goal is to find the maximum CPU load at any time if 8 | * all the jobs are running on the same machine. 9 | * 10 | * Example 1: 11 | * Jobs: [[1,4,3], [2,5,4], [7,9,6]] 12 | * Output: 7 13 | * Explanation: Since [1,4,3] and [2,5,4] overlap, their maximum CPU load (3+4=7) 14 | * will be when both the jobs are running at the same time i.e., during the time 15 | * interval (2,4). 16 | * 17 | * Example 2: 18 | * Jobs: [[6,7,10], [2,4,11], [8,12,15]] 19 | * Output: 15 20 | * Explanation: None of the jobs overlap, therefore we will take the maximum load of 21 | * any job which is 15. 22 | * 23 | * Time: O(nlog(n)) 24 | * Space: O(n) <- sorting and heap 25 | * 26 | * Note: this problem is exactly the same as 253_meeting-rooms-ii, check there for 27 | * detailed explanation. The only difference is: instead of the size of the container, 28 | * this problem needs to calculate the load of the all jobs in the containers. Keep 29 | * in mind, the container only stores the jobs which are overlapped. 30 | * 31 | * @param {number[][]} jobs 32 | * @return {number} 33 | */ 34 | function maximumCPULoad(jobs) { 35 | // O(nlog(n)) 36 | jobs.sort((a, b) => a[0] - b[0]); 37 | const smallEndPriorityComparator = (a, b) => b[1] - a[1]; 38 | const minHeap = new Heap(smallEndPriorityComparator); 39 | let maxLoad = 0; 40 | let currentLoad = 0; 41 | // O(n) 42 | for (let i = 0; i < jobs.length; i++) { 43 | while (minHeap.size() > 0 && minHeap.peek()[1] < jobs[i][0]) { 44 | // O(log(n)) 45 | const top = minHeap.pop(); 46 | currentLoad -= top[2]; 47 | } 48 | // O(log(n)) 49 | minHeap.push(jobs[i]); 50 | currentLoad += jobs[i][2]; 51 | maxLoad = Math.max(maxLoad, currentLoad); 52 | } 53 | return maxLoad; 54 | } 55 | 56 | // Test 57 | console.log( 58 | maximumCPULoad([ 59 | [1, 4, 3], 60 | [2, 5, 4], 61 | [7, 9, 6], 62 | ]) 63 | ); // 7 64 | console.log( 65 | maximumCPULoad([ 66 | [6, 7, 10], 67 | [2, 4, 11], 68 | [8, 12, 15], 69 | ]) 70 | ); // 15 71 | -------------------------------------------------------------------------------- /3_fast-slow-pointers/141_linked-list-cycle.js: -------------------------------------------------------------------------------- 1 | const { buildLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given the head of a Singly LinkedList, write a function to determine if the 7 | * LinkedList has a cycle in it or not. 8 | * https://leetcode.com/problems/linked-list-cycle/ 9 | * 10 | * Time: O(n) 11 | * Space: O(1) 12 | * 13 | * @param {ListNode} head 14 | * @return {boolean} 15 | */ 16 | function detectCycleInLinkedList(head) { 17 | let slowPointer = head; 18 | let fastPointer = head; 19 | 20 | /** 21 | * No need to check the validity of `slowPointer` because if `fastPointer` is valid, 22 | * then `slowPointer` must be valid. 23 | */ 24 | while (fastPointer && fastPointer.next) { 25 | slowPointer = slowPointer.next; 26 | fastPointer = fastPointer.next.next; 27 | 28 | /** 29 | * Why? When the fast pointer is approaching the slow pointer from behind we have 30 | * two possibilities: 31 | * 1. The fast pointer is one step behind the slow pointer. 32 | * 2. The fast pointer is two steps behind the slow pointer. 33 | * All other distances between the fast and slow pointers will reduce to one of 34 | * these two possibilities. Let’s analyze these scenarios, considering the fast 35 | * pointer always moves first: 36 | * 1. If the fast pointer is one step behind the slow pointer: The fast pointer 37 | * moves two steps and the slow pointer moves one step, and they both meet. 38 | * 2. If the fast pointer is two steps behind the slow pointer: The fast pointer 39 | * moves two steps and the slow pointer moves one step. After the moves, the fast 40 | * pointer will be one step behind the slow pointer, which reduces this scenario 41 | * to the first scenario. This means that the two pointers will meet in the next 42 | * iteration. 43 | */ 44 | if (slowPointer === fastPointer) { 45 | return true; 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | // Test 52 | const head1 = buildLinkedList([1, 2, 3, 4, 5, 6]); 53 | head1.next.next.next.next.next.next = head1.next.next.next; 54 | console.log(detectCycleInLinkedList(head1)); // true 55 | 56 | const head2 = buildLinkedList([2, 4, 6, 8, 10]); 57 | console.log(detectCycleInLinkedList(head2)); // false 58 | -------------------------------------------------------------------------------- /3_fast-slow-pointers/202_happy-number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Any number will be called a happy number if, after repeatedly replacing it with a 5 | * number equal to the sum of the square of all of its digits, leads us to number 6 | * '1'. All other (not-happy) numbers will never reach '1'. Instead, they will be 7 | * stuck in a cycle of numbers which does not include '1'. 8 | * https://leetcode.com/problems/happy-number/ 9 | * 10 | * Example 1: 11 | * Input: 23 12 | * Output: true (23 is a happy number) 13 | * Explanations: Here are the steps to find out that 23 is a happy number: 14 | * 2^2 + 3^2 = 4 + 9 = 13 15 | * 1^2 + 3^2 = 1 + 9 = 10 16 | * 1^2 + 0^2 = 1 + 0 = 1 17 | * 18 | * Example 2: 19 | * Input: 12 20 | * Output: false (12 is not a happy number) 21 | * Explanations: Here are the steps to find out that 12 is not a happy number: 22 | * 1^2 + 2^2 = 1 + 4 = 5 23 | * 5^2 = 25 24 | * 2^2 + 5^2 = 4 + 25 = 29 25 | * 2^2 + 9^2 = 4 + 81 = 85 26 | * 8^2 + 5^2 = 64 + 25 = 89 27 | * 8^2 + 9^2 = 64 + 81 = 145 28 | * 1^2 + 4^2 + 5^2 = 1 + 16 + 25 = 42 29 | * 4^2 + 2^2 = 16 + 4 = 20 30 | * 2^2 + 0^2 = 4 + 0 = 4 31 | * 4^2 = 16 32 | * 1^2 + 6^2 = 1 + 36 = 37 33 | * 3^2 + 7^2 = 9 + 49 = 58 34 | * 5^2 + 8^2 = 25 + 64 = 89 (loop) 35 | * 36 | * 37 | * Time: O(log(n)) (@TODO no idea why) 38 | * Space: O(1) 39 | * 40 | * Note: similar as cycle detection in linked list, regardless of happy or not happy 41 | * number, it always enters into a cycle (happy number's cycle is 1), so we can 42 | * utilize the same fast/slow pointer strategy. 43 | * 44 | * @param {number} number 45 | * @return {boolean} 46 | */ 47 | function isHappyNumber(number) { 48 | let slow = number; 49 | let fast = number; 50 | 51 | while (true) { 52 | slow = _getSumOfAllDigitsSquare(slow); 53 | fast = _getSumOfAllDigitsSquare(_getSumOfAllDigitsSquare(fast)); 54 | 55 | if (slow === fast) { 56 | return slow === 1; 57 | } 58 | } 59 | } 60 | 61 | function _getSumOfAllDigitsSquare(number) { 62 | let sum = 0; 63 | while (number !== 0) { 64 | const digit = number % 10; 65 | sum += digit * digit; 66 | number = Math.floor(number / 10); 67 | } 68 | return sum; 69 | } 70 | 71 | // Test 72 | console.log(isHappyNumber(23)); // true 73 | console.log(isHappyNumber(12)); // false 74 | -------------------------------------------------------------------------------- /11_binary-search/744_find-smallest-letter-greater-than-target.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of lowercase letters sorted in ascending order, find the smallest 5 | * letter in the given array greater than a given "key". Assume the given array is a 6 | * circular list, which means that the last letter is assumed to be connected with the 7 | * first letter. This also means that the smallest letter in the given array is greater 8 | * than the last letter of the array and is also the first letter of the array. Write a 9 | * function to return the next letter of the given "key". 10 | * https://leetcode.com/problems/find-smallest-letter-greater-than-target/ 11 | * 12 | * Example 1: 13 | * Input: ['a', 'c', 'f', 'h'], key = 'f' 14 | * Output: 'h' 15 | * Explanation: The smallest letter greater than 'f' is 'h' in the given array. 16 | * 17 | * Example 2: 18 | * Input: ['a', 'c', 'f', 'h'], key = 'm' 19 | * Output: 'a' 20 | * Explanation: As the array is assumed to be circular, the smallest letter greater than 21 | * 'm' is 'a'. 22 | * 23 | * 24 | * Time: O(log n) 25 | * Space: O(1) 26 | * 27 | * @param {string[]} letters 28 | * @param {string} key 29 | * @return {string} 30 | */ 31 | function findSmallestLetterGreaterThanTarget(letters, key) { 32 | let start = 0; 33 | let end = letters.length; 34 | while (start < end) { 35 | const middle = start + Math.floor((end - start) / 2); 36 | // char can be compared directly here 37 | if (letters[middle] > key) { 38 | end = middle; 39 | } else { 40 | start = middle + 1; 41 | } 42 | } 43 | /** 44 | * Follow the binary search, the only difference is here we need to return the 45 | * "mod" of `start` here. Remember `start` is always the smallest index which 46 | * can satisfy the "end change condition" (e.g. `letters[middle] > key`, the 47 | * smallest index which is greater than the target, exactly as the question asked), 48 | * but remember the index can equal to `letters.length` (example 2 above), that's 49 | * why "mod" here is required. 50 | */ 51 | return letters[start % letters.length]; 52 | } 53 | 54 | // Test 55 | console.log(findSmallestLetterGreaterThanTarget(['a', 'c', 'f', 'h'], 'f')); // 'h' 56 | console.log(findSmallestLetterGreaterThanTarget(['a', 'c', 'f', 'h'], 'm')); // 'a' 57 | -------------------------------------------------------------------------------- /4_merge-intervals/README.md: -------------------------------------------------------------------------------- 1 | # Merge intervals 2 | 3 | ## When to use 4 | 5 | This pattern describes an efficient technique to deal with overlapping intervals. In a lot of problems involving intervals, we either need to find overlapping intervals or merge intervals if they overlap. 6 | 7 | ## Key concepts 8 | 9 | Given two intervals ("a" and "b"), there will be six different ways the two intervals can relate to each other: 10 | 11 | 1. "a" and "b" do not overlap 12 | 13 | ``` 14 | |_____| |_____| 15 | a b 16 | ``` 17 | 18 | 2. "a" and "b" overlap, "b" ends after "a" 19 | 20 | ``` 21 | |_____| a 22 | |_____| b 23 | ``` 24 | 25 | 3. "a" completely overlaps "b" 26 | 27 | ``` 28 | |_______| a 29 | |_____| b 30 | ``` 31 | 32 | 4. "a" and "b" overlap, "a" ends after "b" 33 | 34 | ``` 35 | |_____| a 36 | |_____| b 37 | ``` 38 | 39 | 5. "a" and "b" do not overlap 40 | 41 | ``` 42 | |_____| |_____| 43 | b a 44 | ``` 45 | 46 | 6. "a" completely overlaps "b" 47 | 48 | ``` 49 | |_____| a 50 | |_______| b 51 | ``` 52 | 53 | To easily check if the two intervals overlap, we can think about the not overlap condition first, and then do a `not`: 54 | 55 | ```javascript 56 | // do not overlap 57 | end1 < start2 || end2 < start1; // scenario 1 and 5 58 | // overlap 59 | !(end1 < start2 || end2 < start1); // => 60 | end1 >= start2 && end2 >= start1; 61 | ``` 62 | 63 | The common strategy is to sort all the intervals by their "start" first, if two intervals are overlapped, we can calculate: 64 | 65 | - merged interval: `[ Math.min(start1, start2), Math.max(end1, end2) ]` 66 | - intersection: `[ Math.max(start1, start2), Math.min(end1, end2) ]` 67 | 68 | To calculate the overlapped interval count at any time (e.g. calculate the max overlapped interval count for all time), the most common data structure we use is the minimum heap: we maintain a heap to store all the intervals, before inserting a new interval to the heap, we remove all intervals from the heap whose end time is smaller than the start time of the interval to be inserted (e.g. remove all the finished intervals), the size of the heap is the maximum overlapped interval count. (e.g. [0_maximum-cpu-load](./0_maximum-cpu-load.js) and [253_meeting-rooms-ii](./253_meeting-rooms-ii.js)) 69 | -------------------------------------------------------------------------------- /2_two-pointers/844_backspace-string-compare.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given two strings containing backspaces (identified by the character ‘#’), check if the 5 | * two strings are equal. Note that after backspacing an empty text, the text will 6 | * continue empty (e.g. "a##" => "", "#" => "") 7 | * https://leetcode.com/problems/backspace-string-compare/ 8 | * 9 | * Example 1: 10 | * Input: str1="xy#z", str2="xzz#" 11 | * Output: true 12 | * Explanation: After applying backspaces the strings become "xz" and "xz" respectively. 13 | * 14 | * Example 2: 15 | * Input: str1="xy#z", str2="xyz#" 16 | * Output: false 17 | * Explanation: After applying backspaces the strings become "xz" and "xy" respectively. 18 | * 19 | * Time: O(m + n) 20 | * Space: O(1) 21 | * 22 | * @param {string} str1 23 | * @param {string} str2 24 | * @return {boolean} 25 | */ 26 | function backspaceStringCompare(str1, str2) { 27 | // Starting from the end of the string, it's easier to consider the "#". 28 | let i = str1.length - 1; 29 | let j = str2.length - 1; 30 | 31 | while (i >= 0 || j >= 0) { 32 | i = _getNextIndex(str1, i); 33 | j = _getNextIndex(str2, j); 34 | 35 | // both str1 and str2 reach the start of the string 36 | if (i < 0 && j < 0) { 37 | return true; 38 | } 39 | 40 | // only one string reaches the start of the string 41 | if (i < 0 || j < 0) { 42 | return false; 43 | } 44 | 45 | if (str1[i] !== str2[j]) { 46 | return false; 47 | } 48 | // str1[i] === str2[j] => decrease index 49 | i--; 50 | j--; 51 | } 52 | return true; 53 | } 54 | 55 | function _getNextIndex(str, startIndex) { 56 | let nextIndex = startIndex; 57 | let backspaceCount = 0; 58 | 59 | while (nextIndex >= 0) { 60 | if (str[nextIndex] === '#') { 61 | backspaceCount++; 62 | } else if (backspaceCount > 0) { 63 | /** 64 | * Need to consider this scenario: "a#b##", if we use `nextIndex -= backspaceCount`, 65 | * the 2nd "#" will be ignored. 66 | */ 67 | backspaceCount--; 68 | } else { 69 | break; 70 | } 71 | nextIndex--; 72 | } 73 | return nextIndex; 74 | } 75 | 76 | // Test 77 | console.log(backspaceStringCompare('xy#z', 'xzz#')); // true 78 | console.log(backspaceStringCompare('xy#z', 'xyz#')); // false 79 | -------------------------------------------------------------------------------- /11_binary-search/34_find-first-and-last-position-of-element-in-sorted-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of numbers sorted in ascending order, find the range of a given 5 | * number "key". The range of the "key" will be the first and last position of the 6 | * "key" in the array. Write a function to return the range of the "key". If the 7 | * "key" is not present return [-1, -1]. 8 | * 9 | * Example 1: 10 | * Input: [4, 6, 6, 6, 9], key = 6 11 | * Output: [1, 3] 12 | * 13 | * Example 2: 14 | * Input: [1, 3, 8, 10, 15], key = 12 15 | * Output: [-1, -1] 16 | * 17 | * 18 | * Time: O(log n) 19 | * Space: O(1) 20 | * 21 | * @param {number[]} nums 22 | * @param {number} key 23 | * @return {number[]} 24 | */ 25 | function findNumberRange(nums, key) { 26 | let start1 = 0; 27 | let end1 = nums.length; 28 | while (start1 < end1) { 29 | const middle1 = start1 + Math.floor((end1 - start1) / 2); 30 | if (nums[middle1] >= key) { 31 | end1 = middle1; 32 | } else { 33 | start1 = middle1 + 1; 34 | } 35 | } 36 | /** 37 | * `start1` is the smallest index which can satisfy `nums[middle1] >= key`, e.g. 38 | * the first position of the `key`. If it's not equal to key, which means the `key` 39 | * is not existed. 40 | */ 41 | if (nums[start1] !== key) { 42 | return [-1, -1]; 43 | } 44 | /** 45 | * If the next index of `start1` is not equal to `key`, which mean `key` is unique 46 | * in the array, we can return directly. 47 | */ 48 | if (nums[start1 + 1] !== key) { 49 | return [start1, start1]; 50 | } 51 | 52 | let start2 = 0; 53 | let end2 = nums.length; 54 | while (start2 < end2) { 55 | const middle2 = start2 + Math.floor((end2 - start2) / 2); 56 | if (nums[middle2] > key) { 57 | end2 = middle2; 58 | } else { 59 | start2 = middle2 + 1; 60 | } 61 | } 62 | /** 63 | * `start2` is the smallest index which can satisfy `nums[middle2] > key`, e.g. 64 | * the first position which is greater than `key`, so `start2 - 1` is the last 65 | * position of the `key`. 66 | */ 67 | return [start1, start2 - 1]; 68 | } 69 | 70 | // Test 71 | console.log(findNumberRange([4, 6, 6, 6, 9], 6)); // [1, 3] 72 | console.log(findNumberRange([1, 3, 8, 10, 15], 12)); // [-1, -1] 73 | console.log(findNumberRange([1, 3, 8, 10, 15], 10)); // [3, 3] 74 | -------------------------------------------------------------------------------- /4_merge-intervals/57_insert-interval.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a list of non-overlapping intervals sorted by their start time, insert a 5 | * given interval at the correct position and merge all necessary intervals to 6 | * produce a list that has only mutually exclusive intervals. 7 | * https://leetcode.com/problems/insert-interval/ 8 | * 9 | * Example 1: 10 | * Input: Intervals=[[1,3], [5,7], [8,12]], New Interval=[4,6] 11 | * Output: [[1,3], [4,7], [8,12]] 12 | * Explanation: After insertion, since [4,6] overlaps with [5,7], we merged them into 13 | * one [4,7]. 14 | * 15 | * Example 2: 16 | * Input: Intervals=[[1,3], [5,7], [8,12]], New Interval=[4,10] 17 | * Output: [[1,3], [4,12]] 18 | * Explanation: After insertion, since [4,10] overlaps with [5,7] & [8,12], we merged 19 | * them into [4,12]. 20 | * 21 | * Time: O(n) 22 | * Space: O(1) 23 | * 24 | * @param {number[][]} intervals 25 | * @param {number[]} newInterval 26 | * @return {number[][]} 27 | */ 28 | function insertInterval(intervals, newInterval) { 29 | const result = []; 30 | /** 31 | * Only the intervals meets intervals[i][1] >= newInterval[0] or 32 | * intervals[i][0] <= newInterval[1] can be overlapped with the 33 | * newInterval, in addition, all these overlapped intervals will 34 | * be combined into one interval. 35 | */ 36 | let i = 0; 37 | while (i < intervals.length && intervals[i][1] < newInterval[0]) { 38 | result.push(intervals[i]); 39 | i++; 40 | } 41 | let combinedInterval = newInterval; 42 | while (i < intervals.length && intervals[i][0] <= newInterval[1]) { 43 | combinedInterval = [ 44 | Math.min(combinedInterval[0], intervals[i][0]), 45 | Math.max(combinedInterval[1], intervals[i][1]), 46 | ]; 47 | i++; 48 | } 49 | result.push(combinedInterval); 50 | while (i < intervals.length) { 51 | result.push(intervals[i]); 52 | i++; 53 | } 54 | 55 | return result; 56 | } 57 | 58 | // Test 59 | const result1 = insertInterval( 60 | [ 61 | [1, 3], 62 | [5, 7], 63 | [8, 12], 64 | ], 65 | [4, 6] 66 | ); 67 | result1.forEach((i) => console.log(i)); // [[1,3], [4,7], [8,12]] 68 | const result2 = insertInterval( 69 | [ 70 | [1, 3], 71 | [5, 7], 72 | [8, 12], 73 | ], 74 | [4, 10] 75 | ); 76 | result2.forEach((i) => console.log(i)); // [[1,3], [4,12]] 77 | -------------------------------------------------------------------------------- /1_sliding-window/3_longest-substring-without-repeating-characters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a string, find the length of the longest substring without repeating characters. 5 | * https://leetcode.com/problems/longest-substring-without-repeating-characters/ 6 | * 7 | * Example 1: 8 | * Input: String="aabccbb" 9 | * Output: 3 10 | * Explanation: The longest substring without any repeating characters is "abc". 11 | * 12 | * Example 2: 13 | * Input: String="abbbb" 14 | * Output: 2 15 | * Explanation: The longest substring without any repeating characters is "ab". 16 | * 17 | * Time: O(n) <- O(n + n) 18 | * Space: O(n) <- map 19 | * 20 | * @param {string} string 21 | * @return {number} 22 | */ 23 | function longestSubstringWithoutRepeatingCharacters(string) { 24 | let windowStart = 0; 25 | let windowEnd = 0; 26 | 27 | const lastCharIndexMap = {}; 28 | let maxDistinctStringLength = 0; 29 | 30 | // O(n) -> `windowEnd` will run through all characters in the `string` 31 | while (windowEnd < string.length) { 32 | const endChar = string[windowEnd]; 33 | /** 34 | * Note: 35 | * * a special strategy is used here to move `windowStart` forward, the traditional 36 | * way (`windowStart++`) still works but this way is more efficient. When the new 37 | * character to be added is already in the window, we need to shrink the window to make 38 | * sure it only contains distinct character, so we need to find the position where the 39 | * previous `endChar` appears, and move `windowStart` to the next position. 40 | * * use `Math.max(windowStart, ...)` here because `lastCharIndexMap[endChar]` might not 41 | * in the current window now. 42 | */ 43 | if (endChar in lastCharIndexMap) { 44 | windowStart = Math.max(windowStart, lastCharIndexMap[endChar] + 1); 45 | } 46 | lastCharIndexMap[endChar] = windowEnd; 47 | 48 | windowEnd++; 49 | 50 | // Note: `windowEnd - windowStart` because `windowEnd` is not in window yet 51 | maxDistinctStringLength = Math.max( 52 | maxDistinctStringLength, 53 | windowEnd - windowStart 54 | ); 55 | } 56 | 57 | return maxDistinctStringLength; 58 | } 59 | 60 | // Test 61 | console.log(longestSubstringWithoutRepeatingCharacters('aabccbb')); // 3 62 | console.log(longestSubstringWithoutRepeatingCharacters('abbbb')); // 2 63 | -------------------------------------------------------------------------------- /8_DFS/README.md: -------------------------------------------------------------------------------- 1 | # Depth First Search 2 | 3 | ## When to use 4 | 5 | Any problem involving the traversal of a tree in recursive mode can be efficiently solved by using this approach. We will be using recursion (or we can also use a stack for the iterative approach) to keep track of all the previous (parent) nodes while traversing. This also means that the space complexity of the algorithm will be `O(H)`, where `H` is the maximum height of the tree. 6 | 7 | ## Pseudo code 8 | 9 | ```javascript 10 | // pre-order 11 | function preOrder(node, doVisit) { 12 | if (!node) { 13 | return; 14 | } 15 | doVisit(node); 16 | preOrder(node.left, doVisit); 17 | preOrder(node.right, doVisit); 18 | } 19 | 20 | // in-order 21 | function inOrder(node, doVisit) { 22 | if (!node) { 23 | return; 24 | } 25 | inOrder(node.left, doVisit); 26 | doVisit(node); 27 | inOrder(node.right, doVisit); 28 | } 29 | 30 | // post-order 31 | function postOrder(node, doVisit) { 32 | if (!node) { 33 | return; 34 | } 35 | postOrder(node.left, doVisit); 36 | postOrder(node.right, doVisit); 37 | doVisit(node); 38 | } 39 | 40 | // more general DFS 41 | function recursion(node, aggregateValue) { 42 | if (!node) { 43 | return; 44 | } 45 | if (!node.left && !node.right) { 46 | doSomethingWithLeafNode(); 47 | } 48 | addCurrentNodeToAggregation(node, aggregateValue); 49 | recursion(node.left); 50 | recursion(node.right); 51 | removeCurrentNodeFromAggregation(node, aggregateValue); 52 | } 53 | ``` 54 | 55 | Note: 56 | 57 | - pre/in/post order here is related to the parent node, e.g. visit parent node first/in the middle/last, for children node visiting order, it's always left to right. 58 | - if `doVisit` is not pure, e.g. modify some object value reused by the traversal, then after visiting, it should revert the change before traversing the next node, check `addCurrentNodeToAggregation/removeCurrentNodeFromAggregation` above. 59 | - keep in mind how to use the return value of the recursion to prevent creating unnecessary global variable. 60 | - sometimes we need to think more about how to use the left/right sub tree aggregation value to calculate the parent node's aggregation value, check questions [124_binary-tree-maximum-path-sum](./124_binary-tree-maximum-path-sum.js) and[543_diameter-of-binary-tree](./543_diameter-of-binary-tree.js) for more examples. 61 | -------------------------------------------------------------------------------- /3_fast-slow-pointers/142_linked-list-cycle-ii.js: -------------------------------------------------------------------------------- 1 | const { buildLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given the head of a Singly LinkedList that contains a cycle, write a function to 7 | * find the starting node of the cycle. 8 | * https://leetcode.com/problems/linked-list-cycle-ii/ 9 | * 10 | * Time: O(n) 11 | * Space: O(1) 12 | * 13 | * @param {ListNode} head 14 | * @return {ListNode} 15 | */ 16 | function findCycleStartInLinkedList(head) { 17 | let slowPointer = head; 18 | let fastPointer = head; 19 | 20 | let hasCycle = false; 21 | while (fastPointer && fastPointer.next) { 22 | slowPointer = slowPointer.next; 23 | fastPointer = fastPointer.next.next; 24 | if (slowPointer === fastPointer) { 25 | hasCycle = true; 26 | break; 27 | } 28 | } 29 | if (!hasCycle) { 30 | return null; 31 | } 32 | /** 33 | * Consider the linked list as this: 34 | * head (k1) start of cycle 35 | * o----------o-------o---- 36 | * | (k2) meet | 37 | * ------------ 38 | * 39 | * slowPointerPath: slow = k1 (between "head" and "start of cycle") + k2 (between 40 | * "start of cycle" and "meet point") 41 | * fastPointerPath: fast = k1 + 2 * k2 + (cycle length - k2) = k1 + k2 + cycle length 42 | * 43 | * While at the same time, we have fast = 2(k1 + k2), so we can deduce: 44 | * cycle length = k1 + k2, so the distance between "meet point" and "start of cycle" 45 | * is also k1 (cycle length - k2). 46 | * 47 | * Then what we need to do is just put pointer1 at "head", put pointer2 at the meet 48 | * point (`slow`), they will meet after they both finish k1 steps, which is exactly 49 | * the "start of cycle". 50 | */ 51 | let pointer1 = head; 52 | let pointer2 = slowPointer; 53 | while (pointer1 !== pointer2) { 54 | pointer1 = pointer1.next; 55 | pointer2 = pointer2.next; 56 | } 57 | return pointer1; 58 | } 59 | 60 | // Test 61 | const head = buildLinkedList([1, 2, 3, 4, 5, 6]); 62 | 63 | head.next.next.next.next.next.next = head.next.next; 64 | console.log(findCycleStartInLinkedList(head).val); // 3 65 | 66 | head.next.next.next.next.next.next = head.next.next.next; 67 | console.log(findCycleStartInLinkedList(head).val); // 4 68 | 69 | head.next.next.next.next.next.next = head; 70 | console.log(findCycleStartInLinkedList(head).val); // 1 71 | -------------------------------------------------------------------------------- /11_binary-search/81_search-in-rotated-sorted-array-ii.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Follow up on the 33_search-in-rotated-sorted-array problem, how do we search in a 5 | * sorted and rotated array that also has duplicates? The return value should be a 6 | * boolean. 7 | * https://leetcode.com/problems/search-in-rotated-sorted-array-ii/ 8 | * 9 | * Example 1: 10 | * Input: [3, 7, 3, 3, 3], key = 7 11 | * Output: true 12 | * 13 | * Example 2: 14 | * Input: [2, 5, 6, 0, 0, 1, 2], key = 3 15 | * Output: false 16 | * 17 | * 18 | * Time: O(n) in worst case, e.g. searching 5 in [3, 3, 3, 3, 3] => n/2, so the average 19 | * time complexity should be O(log n ~ 2/n) -> O(log n) ~ O(n). 20 | * Space: O(1) 21 | * 22 | * @param {number[]} nums 23 | * @param {number} key 24 | * @return {number} 25 | */ 26 | function searchRotatedArrayWithDuplicates(nums, key) { 27 | let start = 0; 28 | let end = nums.length - 1; 29 | while (start <= end) { 30 | const middle = start + Math.floor((end - start) / 2); 31 | if (nums[middle] === key) { 32 | return true; 33 | } 34 | /** 35 | * The only difference from problem 33_search-in-rotated-sorted-array is here. 36 | * Think about the [3, 7, 3, 3, 3], the 1st loop `middle = 3` and `start = 3`, 37 | * (`nums[start] === nums[middle]`), in this case, it's guaranteed that we also 38 | * have `nums[middle] === nums[end]` (otherwise the array is not rotated sorted). 39 | * So we can't decide which side is sorted for this scenario, all we can do is to 40 | * increase the `start` and decrease the `end` to shrink the search range (shrink 41 | * by 2). 42 | */ 43 | if (nums[start] === nums[middle] && nums[end] === nums[middle]) { 44 | start++; 45 | end--; 46 | } else if (nums[start] <= nums[middle]) { 47 | // [start, middle] is sorted 48 | if (key >= nums[start] && key < nums[middle]) { 49 | end = middle - 1; 50 | } else { 51 | start = middle + 1; 52 | } 53 | } else { 54 | // [middle, end] is sorted 55 | if (key > nums[middle] && key <= nums[end]) { 56 | start = middle + 1; 57 | } else { 58 | end = middle - 1; 59 | } 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | // Test 66 | console.log(searchRotatedArrayWithDuplicates([3, 7, 3, 3, 3], 7)); // true 67 | console.log(searchRotatedArrayWithDuplicates([2, 5, 6, 0, 0, 1, 2], 3)); // false 68 | -------------------------------------------------------------------------------- /2_two-pointers/259_3sum-smaller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array arr of unsorted numbers and a target sum, count all triplets in it such 5 | * that arr[i] + arr[j] + arr[k] < target where i, j, and k are three different indices. 6 | * Write a function to return the count of such triplets. 7 | * https://leetcode.com/problems/3sum-smaller/ (subscription) 8 | * 9 | * Example 1: 10 | * Input: [-1, 0, 2, 3], target=3 11 | * Output: 2 12 | * Explanation: There are two triplets whose sum is less than the target: [-1, 0, 3], 13 | * [-1, 0, 2] 14 | * 15 | * Example 2: 16 | * Input: [-1, 4, 2, 1, 3], target=5 17 | * Output: 4 18 | * Explanation: There are four triplets whose sum is less than the target: [-1, 1, 4], 19 | * [-1, 1, 3], [-1, 1, 2], [-1, 2, 3] 20 | * 21 | * Time: O(n^2) <- O(nlog(n)) + O(n^2) 22 | * Space: O(n) <- merge sort 23 | * 24 | * Note: similar as 15_3sum.js, check there for detailed explanation. 25 | * 26 | * @param {number[]} array 27 | * @param {number} target 28 | * @return {number} 29 | */ 30 | function tripletsCountWithSmallerSum(array, target) { 31 | // O(nlog(n)) 32 | array.sort((a, b) => a - b); 33 | 34 | let count = 0; 35 | 36 | // O(n) 37 | for (let i = 0; i <= array.length - 3; i++) { 38 | let left = i + 1; 39 | let right = array.length - 1; 40 | 41 | // O(n) 42 | while (left < right) { 43 | const sum = array[i] + array[left] + array[right]; 44 | if (sum < target) { 45 | /** 46 | * If a valid sum is found, since the array is ordered, this `left` can be paired 47 | * with all numbers between `left`(exclusive) and `right`(inclusive). 48 | * Example: [-1, 1, 2, 3, 4] with target 5, first loop with i = 0, array[i] = -1, 49 | * left = 1, right 4, the sum = 4 < 5, so (1,2) (1,3) (1,4) are all valid. 50 | */ 51 | count += right - left; 52 | /** 53 | * After that we still need to move on to see if the valid pair with the next 54 | * `left`. Use the above example, we need to do `left++` here so we can consider 55 | * left = 2, right = 4, cause (2,3) is also a valid pair. 56 | */ 57 | left++; 58 | } else { 59 | right--; 60 | } 61 | } 62 | } 63 | return count; 64 | } 65 | 66 | // Test 67 | console.log(tripletsCountWithSmallerSum([-1, 0, 2, 3], 3)); // 2 68 | console.log(tripletsCountWithSmallerSum([-1, 4, 2, 1, 3], 5)); // 4 69 | -------------------------------------------------------------------------------- /7_BFS/103_binary-tree-zigzag-level-order-traversal.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree, populate an array to represent its zigzag level order 7 | * traversal. You should populate the values of all nodes of the first level from 8 | * left to right, then right to left for the next level and keep alternating in the 9 | * same manner for the following levels. 10 | * https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/ 11 | * 12 | * Example 1: 13 | * Input: [1, 2, 3, 4, 5, 6, 7] 14 | * Output: [[1], [3, 2], [4, 5, 6, 7]] 15 | * 16 | * Example 2: 17 | * Input: [12, 7, 1, null, 9, 10, 5, null, null, 20, 17] 18 | * Output: [[12], [1, 7], [9, 10, 5], [17, 20]] 19 | * 20 | * Time: O(n) 21 | * Space: O(n) 22 | * 23 | * @param {TreeNode} root 24 | * @return {number[][]} 25 | */ 26 | function zigzagLevelOrderTraverse(root) { 27 | if (!root) { 28 | return []; 29 | } 30 | 31 | const result = []; 32 | const levelNodes = [root]; 33 | let leftToRight = true; 34 | while (levelNodes.length > 0) { 35 | const levelLength = levelNodes.length; 36 | const values = []; 37 | for (let i = 0; i < levelLength; i++) { 38 | const node = levelNodes.shift(); 39 | /** 40 | * Similar as 102_binary-tree-level-order-traversal, the only difference is 41 | * here we need to maintain another flag to decide the order, if the order is 42 | * from left to right, use `push` to add at the end, otherwise use `unshift` 43 | * to add the start. Then flip the flag after each level. 44 | */ 45 | if (leftToRight) { 46 | values.push(node.val); 47 | } else { 48 | values.unshift(node.val); 49 | } 50 | if (node.left) { 51 | levelNodes.push(node.left); 52 | } 53 | if (node.right) { 54 | levelNodes.push(node.right); 55 | } 56 | } 57 | result.push(values); 58 | leftToRight = !leftToRight; 59 | } 60 | return result; 61 | } 62 | 63 | // Test 64 | const tree1 = buildTreeBFS([1, 2, 3, 4, 5, 6, 7]); 65 | const result1 = zigzagLevelOrderTraverse(tree1); 66 | result1.forEach((i) => console.log(i)); // [[1], [3, 2], [4, 5, 6, 7]] 67 | 68 | const tree2 = buildTreeBFS([12, 7, 1, null, 9, 10, 5, null, null, 20, 17]); 69 | const result2 = zigzagLevelOrderTraverse(tree2); 70 | result2.forEach((i) => console.log(i)); // [[12], [1, 7], [9, 10, 5], [17, 20]] 71 | -------------------------------------------------------------------------------- /10_subsets/90_subsets-ii.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a set of numbers that might contain duplicates, find all of its distinct 5 | * subsets. 6 | * https://leetcode.com/problems/subsets-ii/ 7 | * 8 | * Example 1: 9 | * Input: [1, 3, 3] 10 | * Output: [], [1], [3], [1,3], [3,3], [1,3,3] 11 | * 12 | * Example 2: 13 | * Input: [1, 5, 3, 3] 14 | * Output: [], [1], [5], [3], [1,5], [1,3], [5,3], [1,5,3], [3,3], [1,3,3], [3,3,5], 15 | * [1,5,3,3] 16 | * 17 | * 18 | * Time:O(n 2^n) 19 | * Space: O(n 2^n) <- result 20 | * 21 | * @param {number[]} nums 22 | * @return {number[][]} 23 | */ 24 | function findSubsetsWithDuplicates(nums) { 25 | // O(n * log(n)) 26 | nums.sort((a, b) => a - b); 27 | const subsets = []; 28 | _backtrack(nums, [], 0, subsets); 29 | return subsets; 30 | } 31 | 32 | function _backtrack(nums, curResult, startIndex, result) { 33 | // O(n) to copy an array 34 | result.push(Array.from(curResult)); 35 | for (let i = startIndex; i < nums.length; i++) { 36 | /** 37 | * This is similar as 78_subsets problem, the only difference here is we need 38 | * to skip the consecutive duplicates. For example, [1, 3, 3], we first process 39 | * 1 and get [1], and then process the first 3 and get [1, 3], and then second 3 40 | * [1, 3, 3] (refer to the tree in 78_subsets), then back to [1, 3], then 41 | * back to [1], at this moment (`startIndex = 0` and `i = 2`), if we process i = 2 42 | * we will get duplicate record [1, 3], so the logic here is to skip this one. 43 | * 44 | * So basically we need to check `i` and `i - 1` when `i > startIndex`, but 45 | * when `i === startIndex`, we still need to process the duplicates, think about 46 | * [3, 3]. 47 | * 48 | * Note: in order to make it work, we need to sort the array to make sure the 49 | * duplicates always appear together. 50 | */ 51 | if (i > startIndex && nums[i] === nums[i - 1]) { 52 | continue; 53 | } 54 | curResult.push(nums[i]); 55 | _backtrack(nums, curResult, i + 1, result); 56 | curResult.pop(); 57 | } 58 | } 59 | 60 | // Test 61 | const result1 = findSubsetsWithDuplicates([1, 3, 3]); 62 | result1.forEach((i) => console.log(i)); // [[], [1], [1,3], [1,3,3], [3], [3,3]] 63 | 64 | const result2 = findSubsetsWithDuplicates([1, 5, 3, 3]); 65 | result2.forEach((i) => console.log(i)); // [[], [1], [1,5], [1,5,3], [1,5,3,3], [1,3], [1,3,3], [5], [5,3], [5,3,3], [3], [3,3]] 66 | -------------------------------------------------------------------------------- /11_binary-search/0_search-bitonic-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a Bitonic array, find if a given 'key' is present in it. An array is considered 5 | * bitonic if it is monotonically increasing and then monotonically decreasing. 6 | * Monotonically increasing or decreasing means that for any index i in the array 7 | * `arr[i] != arr[i+1]`. Write a function to return the index of the 'key'. If the 'key' 8 | * is not present, return -1. 9 | * 10 | * Example 1: 11 | * Input: [1, 3, 8, 4, 3], key=4 12 | * Output: 3 13 | * 14 | * Example 2: 15 | * Input: [3, 8, 3, 1], key=8 16 | * Output: 1 17 | * 18 | * 19 | * Time: O(log n) 20 | * Space: O(1) 21 | * 22 | * @param {number[]} nums 23 | * @param {number} key 24 | * @return {number} 25 | */ 26 | function searchBitonicArray(nums, key) { 27 | // find the maximum in the bitonic with 162_bitonic-array-maximum 28 | let start = 0; 29 | let end = nums.length; 30 | while (start < end) { 31 | const middle = start + Math.floor((end - start) / 2); 32 | if (middle === nums.length - 1 || nums[middle] > nums[middle + 1]) { 33 | end = middle; 34 | } else { 35 | start = middle + 1; 36 | } 37 | } 38 | // start is the maximum value index 39 | const maxIndex = start; 40 | if (nums[maxIndex] === key) { 41 | return maxIndex; 42 | } 43 | if (nums[maxIndex] < key) { 44 | return -1; 45 | } 46 | // binary search between [0, max) - ascending 47 | start = 0; 48 | end = maxIndex; 49 | while (start < end) { 50 | const middle = start + Math.floor((end - start) / 2); 51 | if (nums[middle] >= key) { 52 | end = middle; 53 | } else { 54 | start = middle + 1; 55 | } 56 | } 57 | if (nums[start] === key) { 58 | return start; 59 | } 60 | // binary search between [max, nums.length) - descending 61 | start = maxIndex; 62 | end = nums.length; 63 | while (start < end) { 64 | const middle = start + Math.floor((end - start) / 2); 65 | if (nums[middle] <= key) { 66 | end = middle; 67 | } else { 68 | start = middle + 1; 69 | } 70 | } 71 | return nums[start] === key ? start : -1; 72 | } 73 | 74 | // Test 75 | console.log(searchBitonicArray([1, 3, 8, 4, 3], 4)); // 3 76 | console.log(searchBitonicArray([3, 8, 3, 1], 8)); // 1 77 | console.log(searchBitonicArray([1, 3, 8, 12], 12)); // 3 78 | console.log(searchBitonicArray([10, 9, 8], 10)); // 0 79 | console.log(searchBitonicArray([10, 9, 8], 12)); // -1 80 | -------------------------------------------------------------------------------- /12_bitwise-xor/82_flipping-an-image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a binary matrix representing an image, we want to flip the image horizontally, 5 | * then invert it. To flip an image horizontally means that each row of the image is 6 | * reversed. For example, flipping [0, 1, 1] horizontally results in [1, 1, 0]. To 7 | * invert an image means that each 0 is replaced by 1, and each 1 is replaced by 0. For 8 | * example, inverting [1, 1, 0] results in [0, 0, 1]. 9 | * https://leetcode.com/problems/flipping-an-image/ 10 | * 11 | * Example 1: 12 | * Input: [ 13 | * [1,0,1], 14 | * [1,1,1], 15 | * [0,1,1] 16 | * ] 17 | * Output: [ 18 | * [0,1,0], 19 | * [0,0,0], 20 | * [0,0,1] 21 | * ] 22 | * Explanation: First reverse each row: [[1,0,1],[1,1,1],[1,1,0]]. Then, invert the 23 | * image: [[0,1,0],[0,0,0],[0,0,1]] 24 | * 25 | * 26 | * Example 2: 27 | * Input: [ 28 | * [1,1,0,0], 29 | * [1,0,0,1], 30 | * [0,1,1,1], 31 | * [1,0,1,0] 32 | * ] 33 | * Output: [ 34 | * [1,1,0,0], 35 | * [0,1,1,0], 36 | * [0,0,0,1], 37 | * [1,0,1,0] 38 | * ] 39 | * Explanation: First reverse each row: [[0,0,1,1],[1,0,0,1],[1,1,1,0],[0,1,0,1]]. Then 40 | * invert the image: [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]] 41 | * 42 | * Time: O(n^2) 43 | * Space: O(n^2) <- result 44 | * 45 | * @param {number[][]} matrix 46 | * @return {number[][]} 47 | */ 48 | function flipAndInvertImage(matrix) { 49 | /** 50 | * The idea is simple, just reverse each row and invert it (XOR with 1 to invert 51 | * because 1^1 = 0, 0^1 = 1). 52 | */ 53 | const n = matrix.length; 54 | const middle = Math.floor((n - 1) / 2); 55 | for (let i = 0; i < n; i++) { 56 | /** 57 | * One trick we did here is for each row the loop only needs to run half row 58 | * and we combine the reverse with invert into one step. 59 | */ 60 | for (let j = 0; j <= middle; j++) { 61 | [matrix[i][j], matrix[i][n - 1 - j]] = [ 62 | matrix[i][n - 1 - j] ^ 1, 63 | matrix[i][j] ^ 1, 64 | ]; 65 | } 66 | } 67 | return matrix; 68 | } 69 | 70 | // Test 71 | const result1 = flipAndInvertImage([ 72 | [1, 0, 1], 73 | [1, 1, 1], 74 | [0, 1, 1], 75 | ]); 76 | result1.forEach((i) => console.log(i)); 77 | // [[0,1,0],[0,0,0],[0,0,1]] 78 | const result2 = flipAndInvertImage([ 79 | [1, 1, 0, 0], 80 | [1, 0, 0, 1], 81 | [0, 1, 1, 1], 82 | [1, 0, 1, 0], 83 | ]); 84 | result2.forEach((i) => console.log(i)); 85 | // [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]] 86 | -------------------------------------------------------------------------------- /13_top-k-elements/215_kth-largest-element-in-an-array.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given an unsorted array of numbers, find the Kth largest numbers in it. 7 | * https://leetcode.com/problems/kth-largest-element-in-an-array/ 8 | * 9 | * Example 1: 10 | * Input: [3, 1, 5, 12, 2, 11], K = 3 11 | * Output: 5 12 | * 13 | * Example 2: 14 | * Input: [5, 12, 11, -1, 12], K = 3 15 | * Output: 11 16 | * 17 | * 18 | * Time: O(n log(k)) 19 | * Space: O(k) <- for heap 20 | * 21 | * @param {number[]} nums 22 | * @param {number} k 23 | * @return {number} 24 | */ 25 | function findKthLargestNumber(nums, k) { 26 | /** 27 | * The idea here is, when we iterate through the `nums` array, we keep pushing the 28 | * numbers encountered to an special "array" for the first k numbers, we need to 29 | * somehow maintain these "array" only contains the k largest numbers while the loop 30 | * goes on. Let's assume these k numbers are the k largest numbers, they must follow 31 | * these properties: 32 | * * the remaining n-k numbers must be smaller than the smallest number in the 33 | * "array". 34 | * So when we process the remaining n-k numbers: 35 | * * if the new number encountered is larger than the smallest number in the "array", 36 | * remove the smallest number and insert the new number. 37 | * * if the new number encountered is smaller than the smallest number in the "array", 38 | * do nothing because it can't be part of the k largest numbers group as it's already 39 | * smaller than k numbers. 40 | * 41 | * Follow the idea above, we need to keep getting the smallest numbers in the "array", 42 | * so the best data structure of the "array" should be minimum heap. 43 | * 44 | * Note: the naive solution would be sort the array first and get the kth numbers, 45 | * which would cost O(n log(n)), but with heap solution, the cost will only be 46 | * O(n log(k)). 47 | */ 48 | const minHeap = new Heap((a, b) => b - a); 49 | for (let i = 0; i < nums.length; i++) { 50 | if (i < k) { 51 | // insert O(log k) 52 | minHeap.push(nums[i]); 53 | } else if (nums[i] > minHeap.peek()) { 54 | // remove O(log k) 55 | minHeap.pop(); 56 | // insert O(log k) 57 | minHeap.push(nums[i]); 58 | } 59 | } 60 | return minHeap.peek(); 61 | } 62 | 63 | // Test 64 | console.log(findKthLargestNumber([3, 1, 5, 12, 2, 11], 3)); // 5 65 | console.log(findKthLargestNumber([5, 12, 11, -1, 12], 3)); // 11 66 | -------------------------------------------------------------------------------- /14_k-way-merge/0_kth-smallest-number-in-m-sorted-lists.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given M sorted arrays, find the Kth smallest number among all the arrays. 7 | * 8 | * Example 1: 9 | * Input: L1=[2, 6, 8], L2=[3, 6, 7], L3=[1, 3, 4], K=5 10 | * Output: 4 11 | * Explanation: The 5th smallest number among all the arrays is 4, this can be verified 12 | * from the merged list of all the arrays: [1, 2, 3, 3, 4, 6, 6, 7, 8] 13 | * 14 | * Example 2: 15 | * Input: L1=[5, 8, 9], L2=[1, 7], K=3 16 | * Output: 7 17 | * Explanation: The 3rd smallest number among all the arrays is 7. 18 | * 19 | * 20 | * Time: O((k + m) log(m)) 21 | * Space: O(m) <- for heap 22 | * 23 | * @param {number[][]} lists 24 | * @param {number} k 25 | * @return {number} 26 | */ 27 | function kthSmallestNumberInMSortedLists(lists, k) { 28 | /** 29 | * Similar as the problem 23_merge-k-sorted-lists, the difference here is: 30 | * * the input array is a nested number array, not array of linked lists, so we need to 31 | * store both the `listIndex` and `itemIndex` because we don't have `.next` to find the 32 | * next element of the current popped one 33 | * * we only need to pop() k elements because the problem is asking kth smallest number 34 | */ 35 | const minHeap = new Heap((a, b) => b.value - a.value); 36 | // O(m log(m)) 37 | for (let i = 0; i < lists.length; i++) { 38 | if (lists[i].length > 0) { 39 | minHeap.push({ 40 | value: lists[i][0], 41 | listIndex: i, 42 | itemIndex: 0, 43 | }); 44 | } 45 | } 46 | let count = 0; 47 | // O(k log(m)) pop()/push() will only happen for k times at most 48 | while (minHeap.size() > 0) { 49 | const { value, listIndex, itemIndex } = minHeap.pop(); 50 | count++; 51 | if (count === k) { 52 | return value; 53 | } 54 | const nextItemIndex = itemIndex + 1; 55 | const currentList = lists[listIndex]; 56 | if (nextItemIndex < currentList.length) { 57 | minHeap.push({ 58 | value: currentList[nextItemIndex], 59 | listIndex, 60 | itemIndex: nextItemIndex, 61 | }); 62 | } 63 | } 64 | } 65 | 66 | // Test 67 | console.log( 68 | kthSmallestNumberInMSortedLists( 69 | [ 70 | [2, 6, 8], 71 | [3, 6, 7], 72 | [1, 3, 4], 73 | ], 74 | 5 75 | ) 76 | ); // 4 77 | console.log( 78 | kthSmallestNumberInMSortedLists( 79 | [ 80 | [5, 8, 9], 81 | [1, 7], 82 | ], 83 | 3 84 | ) 85 | ); // 7 86 | -------------------------------------------------------------------------------- /11_binary-search/162_bitonic-array-maximum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Find the maximum index in a given Bitonic array. An array is considered bitonic if it 5 | * is monotonically increasing and then monotonically decreasing. Monotonically 6 | * increasing or decreasing means that for any index i in the array `arr[i] != arr[i+1]`. 7 | * https://leetcode.com/problems/find-peak-element/ 8 | * 9 | * Example 1: 10 | * Input: [1, 3, 8, 12, 4, 2] 11 | * Output: 3 12 | * Explanation: The maximum number in the input bitonic array is '12'. 13 | * 14 | * Example 2: 15 | * Input: [3, 8, 3, 1] 16 | * Output: 1 17 | * 18 | * 19 | * Time: O(log n) 20 | * Space: O(1) 21 | * 22 | * @param {number[]} nums 23 | * @return {number} 24 | */ 25 | function findBitonicArrayMaximum(nums) { 26 | let start = 0; 27 | let end = nums.length; 28 | while (start < end) { 29 | const middle = start + Math.floor((end - start) / 2); 30 | /** 31 | * We are trying to find the 1st element who is larger than its next, that is, 32 | * the smallest index whose value is larger than its next. For binary search, 33 | * if the `middle` is larger than its next, then we need to continue search in the 34 | * left part (i.e. to change `end`), that's why we use 35 | * `nums[middle] > nums[middle + 1]` below. Also remember that the binary search 36 | * will return `start` as the smallest index which can make the "end change 37 | * condition" true, that's exactly what we are looking for. 38 | * 39 | * Note: we need to cater a special condition here: if `middle` is the last index 40 | * we should also change `end` because there's no next element, or we can consider 41 | * the next element is -Infinity. 42 | */ 43 | if (middle === nums.length - 1 || nums[middle] > nums[middle + 1]) { 44 | end = middle; 45 | } else { 46 | start = middle + 1; 47 | } 48 | } 49 | /** 50 | * `start` won't be `nums.length` because when `middle` is the last element above, 51 | * we change `end` directly, that will guarantee `middle + 1 (nums.length)` won't 52 | * be a valid candidate for the final `start`, that's why we don't need to check 53 | * `start === nums.length ? start - 1 : start` here. 54 | */ 55 | return start; 56 | } 57 | 58 | // Test 59 | console.log(findBitonicArrayMaximum([1, 3, 8, 12, 4, 2])); // 3 60 | console.log(findBitonicArrayMaximum([3, 8, 3, 1])); // 1 61 | console.log(findBitonicArrayMaximum([1, 3, 8, 12])); // 3 62 | console.log(findBitonicArrayMaximum([10, 9, 8])); // 0 63 | -------------------------------------------------------------------------------- /10_subsets/40_combination-sum-ii.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a collection of candidate numbers (`candidates`) and a target number 5 | * (`target`), find all unique combinations in `candidates` where the candidate numbers 6 | * sum to `target`. Each number in `candidates` may only be used once in the combination. 7 | * Note: The solution set must not contain duplicate combinations. 8 | * https://leetcode.com/problems/combination-sum-ii/ 9 | * 10 | * Example 1: 11 | * Input: candidates = [10,1,2,7,6,1,5], target = 8 12 | * Output: 13 | * [ 14 | * [1,1,6], 15 | * [1,2,5], 16 | * [1,7], 17 | * [2,6] 18 | * ] 19 | * 20 | * Example 2: 21 | * Input: candidates = [2,5,2,1,2], target = 5 22 | * Output: 23 | * [ 24 | * [1,2,2], 25 | * [5] 26 | * ] 27 | * 28 | * 29 | * Time: O(n 2^n) 30 | * Space: O(2^n) <- result 31 | * 32 | * @param {number[]} candidates 33 | * @param {number} target 34 | * @return {number[][]} 35 | */ 36 | function combinationSum2(candidates, target) { 37 | // O(n * log(n)) 38 | candidates.sort((a, b) => a - b); 39 | const combinations = []; 40 | _backtrack(candidates, target, 0, [], combinations); 41 | return combinations; 42 | } 43 | 44 | /** 45 | * 46 | * @param {number[]} candidates 47 | * @param {number} target 48 | * @param {number} currentIndex 49 | * @param {number[]} currentArr 50 | * @param {number[][]} combinations 51 | */ 52 | function _backtrack( 53 | candidates, 54 | target, 55 | currentIndex, 56 | currentArr, 57 | combinations 58 | ) { 59 | /** 60 | * Similar to the problem 90_subset-ii, the only difference here is for all 61 | * the combinations we need to check their sum against this `target`, only 62 | * push it to the final result when the sum is equal to `target`. 63 | */ 64 | if (target === 0) { 65 | // O(n) to copy an array 66 | combinations.push(Array.from(currentArr)); 67 | return; 68 | } 69 | if (target < 0) { 70 | return; 71 | } 72 | for (let i = currentIndex; i < candidates.length; i++) { 73 | if (i > currentIndex && candidates[i] === candidates[i - 1]) { 74 | continue; 75 | } 76 | currentArr.push(candidates[i]); 77 | _backtrack( 78 | candidates, 79 | target - candidates[i], 80 | i + 1, 81 | currentArr, 82 | combinations 83 | ); 84 | currentArr.pop(); 85 | } 86 | } 87 | 88 | // Test 89 | console.log(combinationSum2([10, 1, 2, 7, 6, 1, 5], 8)); 90 | // [ [1,1,6], [1,2,5], [1,7], [2,6] ] 91 | console.log(combinationSum2([2, 5, 2, 1, 2], 5)); 92 | // [ [1,2,2], [5] ] 93 | -------------------------------------------------------------------------------- /3_fast-slow-pointers/143_reorder-list.js: -------------------------------------------------------------------------------- 1 | const { printLinkedList, buildLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given the head of a Singly LinkedList, write a method to modify the LinkedList 7 | * such that the nodes from the second half of the LinkedList are inserted 8 | * alternately to the nodes from the first half in reverse order. So if the 9 | * LinkedList has nodes 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> null, your method should return 10 | * 1 -> 6 -> 2 -> 5 -> 3 -> 4 -> null. Your algorithm should not use any extra space 11 | * and the input LinkedList should be modified in-place. 12 | * https://leetcode.com/problems/reorder-list/ 13 | * 14 | * 15 | * Example 1: 16 | * Input: 2 -> 4 -> 6 -> 8 -> 10 -> 12 -> null 17 | * Output: 2 -> 12 -> 4 -> 10 -> 6 -> 8 -> null 18 | * 19 | * Example 2: 20 | * Input: 2 -> 4 -> 6 -> 8 -> 10 -> null 21 | * Output: 2 -> 10 -> 4 -> 8 -> 6 -> null 22 | * 23 | * Time: O(n) 24 | * Space: O(1) 25 | * 26 | * @param {ListNode} head 27 | * @return {void} 28 | */ 29 | function reorderLinkedList(head) { 30 | // return directly for empty list or single node list 31 | if (!head || !head.next) { 32 | return; 33 | } 34 | 35 | let slowPointer = head; 36 | let fastPointer = head; 37 | // find middle node (`slowPointer`) O(n/2) 38 | while (fastPointer && fastPointer.next) { 39 | slowPointer = slowPointer.next; 40 | fastPointer = fastPointer.next.next; 41 | } 42 | let startOfSecondHalf = slowPointer.next; 43 | // middle node is the new end 44 | slowPointer.next = null; 45 | // reverse the second half O(n/2) 46 | let prev = null; 47 | let current = startOfSecondHalf; 48 | while (current) { 49 | const next = current.next; 50 | current.next = prev; 51 | prev = current; 52 | current = next; 53 | } 54 | let currentOfFirstHalf = head; 55 | // `prev` is the original end node, and the start node of the reversed second half 56 | let currentOfSecondHalf = prev; 57 | // O(n/2) 58 | while (currentOfSecondHalf) { 59 | // insert into the first half alternatively 60 | const nextOfFirstHalf = currentOfFirstHalf.next; 61 | const nextOfSecondHalf = currentOfSecondHalf.next; 62 | currentOfFirstHalf.next = currentOfSecondHalf; 63 | currentOfSecondHalf.next = nextOfFirstHalf; 64 | currentOfFirstHalf = nextOfFirstHalf; 65 | currentOfSecondHalf = nextOfSecondHalf; 66 | } 67 | } 68 | 69 | // Test 70 | const head = buildLinkedList([2, 4, 6, 8, 10, 12]); 71 | reorderLinkedList(head); 72 | printLinkedList(head); // 2 -> 12 -> 4 -> 10 -> 6 -> 8 73 | -------------------------------------------------------------------------------- /13_top-k-elements/703_kth-largest-element-in-a-stream.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Design a class to efficiently find the Kth largest element in a stream of numbers. 7 | * The class should have the following two things: 8 | * * The constructor of the class should accept an integer array containing initial 9 | * numbers from the stream and an integer K. 10 | * * The class should expose a function add(int num) which will store the given number 11 | * and return the Kth largest number. 12 | * https://leetcode.com/problems/kth-largest-element-in-a-stream/ 13 | * 14 | * Example 1: 15 | * Input: [3, 1, 5, 12, 2, 11], K = 4 16 | * 1. Calling add(6) should return '5'. 17 | * 2. Calling add(13) should return '6'. 18 | * 3. Calling add(4) should still return '6'. 19 | * 20 | */ 21 | class KthLargestNumberInStream { 22 | /** 23 | * 24 | * Time: O(n log(n)) 25 | * Space: O(k) 26 | * 27 | * @param {number} k 28 | * @param {number[]} nums 29 | */ 30 | constructor(k, nums) { 31 | this.minHeap = new Heap((a, b) => b - a); 32 | /** 33 | * Similar as problem 215_kth-largest-element-in-an-array, the difference here is 34 | * when we initialize the class here, `nums.length` might be smaller than `k`, 35 | * that's why we store `k` on the instance cause it will be used in the below `add` 36 | * function. 37 | */ 38 | this.k = k; 39 | for (let i = 0; i < nums.length; i++) { 40 | if (i < k) { 41 | this.minHeap.push(nums[i]); 42 | } else if (nums[i] > this.minHeap.peek()) { 43 | this.minHeap.pop(); 44 | this.minHeap.push(nums[i]); 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * 51 | * Time: O(log(k)) 52 | * 53 | * @param {number} val 54 | */ 55 | add(val) { 56 | /** 57 | * If the heap size is smaller than `k`, it will fall in the above `i < k` condition, 58 | * so we need to push it into the heap anyway. The other logic is the same as the 59 | * logic above in the `constructor()` because heap already has `k` size. 60 | */ 61 | if (this.minHeap.size() < this.k) { 62 | this.minHeap.push(val); 63 | } else if (val > this.minHeap.peek()) { 64 | this.minHeap.pop(); 65 | this.minHeap.push(val); 66 | } 67 | return this.minHeap.peek(); 68 | } 69 | } 70 | 71 | // Test 72 | const kthLargestNumber = new KthLargestNumberInStream(4, [3, 1, 5, 12, 2, 11]); 73 | console.log(kthLargestNumber.add(6)); // 5 74 | console.log(kthLargestNumber.add(13)); // 6 75 | console.log(kthLargestNumber.add(4)); // 6 76 | -------------------------------------------------------------------------------- /4_merge-intervals/253_meeting-rooms-ii.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a list of intervals representing the start and end time of ‘N’ meetings, find 7 | * the minimum number of rooms required to hold all the meetings. 8 | * https://leetcode.com/problems/meeting-rooms-ii/ (subscription) 9 | * 10 | * Example 1: 11 | * Meetings: [[1,4], [2,5], [7,9]] 12 | * Output: 2 13 | * Explanation: Since [1,4] and [2,5] overlap, we need two rooms to hold these two 14 | * meetings. [7,9] can occur in any of the two rooms later. 15 | * 16 | * Example 2: 17 | * Meetings: [[6,7], [2,4], [8,12]] 18 | * Output: 1 19 | * Explanation: None of the meetings overlap, therefore we only need one room to hold 20 | * all meetings. 21 | * 22 | * Time: O(nlog(n)) 23 | * Space: O(n) <- for sorting and the heap 24 | * 25 | * @param {number[][]} meetings 26 | * @return {number} 27 | */ 28 | function minimumMeetingRooms(meetings) { 29 | /** 30 | * The key point of solving this problem is: maintain a meeting container, 31 | * which only contains the meetings which conflict with each other (overlap), 32 | * every time a new meeting is coming, compare the meetings in the container 33 | * with the new meeting, the meetings already finish (whose end is less than 34 | * the new meeting start) can be removed from the container, the maximum size 35 | * of the container is the minimum meeting rooms we need. 36 | * 37 | * What data structure should we use for this container? Since we need to pop 38 | * the smallest end every time, and we need to do remove/insert very often, 39 | * after removing/inserting, the minimum end order needs to be maintained 40 | * correctly. So "minimum heap" is the best data structure. 41 | */ 42 | // O(nlog(n)) 43 | meetings.sort((a, b) => a[0] - b[0]); 44 | const smallEndPriorityComparator = (a, b) => b[1] - a[1]; 45 | const minHeap = new Heap(smallEndPriorityComparator); 46 | let minMeetingRooms = 1; 47 | // O(n) 48 | for (let i = 0; i < meetings.length; i++) { 49 | while (minHeap.size() > 0 && minHeap.peek()[1] <= meetings[i][0]) { 50 | // O(log(n)) 51 | minHeap.pop(); 52 | } 53 | // O(log(n)) 54 | minHeap.push(meetings[i]); 55 | minMeetingRooms = Math.max(minMeetingRooms, minHeap.size()); 56 | } 57 | return minMeetingRooms; 58 | } 59 | 60 | // Test 61 | console.log( 62 | minimumMeetingRooms([ 63 | [1, 4], 64 | [2, 5], 65 | [7, 9], 66 | ]) 67 | ); // 2 68 | console.log( 69 | minimumMeetingRooms([ 70 | [6, 7], 71 | [2, 4], 72 | [8, 12], 73 | ]) 74 | ); // 1 75 | -------------------------------------------------------------------------------- /1_sliding-window/340_longest-substring-with-at-most-k-distinct-characters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a string, find the length of the longest substring in it with no more than 5 | * K distinct characters. 6 | * https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/ 7 | * 8 | * Example 1: 9 | * Input: String="araaci", K=2 10 | * Output: 4 11 | * Explanation: The longest substring with no more than '2' distinct characters is "araa". 12 | * 13 | * Example 2: 14 | * Input: String="araaci", K=1 15 | * Output: 2 16 | * Explanation: The longest substring with no more than '1' distinct characters is "aa". 17 | * 18 | * Time: O(n) <- O(n + n) 19 | * Space: O(n) <- map 20 | * 21 | * @param {string} string 22 | * @param {number} k 23 | * @return {number} 24 | */ 25 | function LongestSubstringWithAtMostKDistinctCharacters(string, k) { 26 | let windowStart = 0; 27 | let windowEnd = 0; 28 | 29 | let maxDistinctStringLength = 0; 30 | const charCountMapInWindow = {}; 31 | 32 | // O(n) -> `windowEnd` run through all characters in the `string` 33 | while (windowEnd < string.length) { 34 | const endChar = string[windowEnd]; 35 | // Aggregation is the count for distinct character in the window 36 | charCountMapInWindow[endChar] = (charCountMapInWindow[endChar] || 0) + 1; 37 | windowEnd++; 38 | 39 | /** 40 | * Keep shrinking the window if the distinct characters in the window (key length of 41 | * `charCountMapInWindow`) is larger than k. 42 | * 43 | * Note: 44 | * * O(n) in total -> `windowStart` run through all characters in the `string` for all 45 | * the outer loops 46 | */ 47 | while (Object.keys(charCountMapInWindow).length > k) { 48 | const startChar = string[windowStart]; 49 | charCountMapInWindow[startChar]--; 50 | if (charCountMapInWindow[startChar] === 0) { 51 | delete charCountMapInWindow[startChar]; 52 | } 53 | windowStart++; 54 | } 55 | 56 | /** 57 | * Even the `charCountMapInWindow` key length is less than `k` (which means we can't get 58 | * k characters), we still need to return the result. 59 | * 60 | * Note: `windowEnd - windowStart` because `windowEnd` is not in window yet 61 | */ 62 | maxDistinctStringLength = Math.max( 63 | maxDistinctStringLength, 64 | windowEnd - windowStart 65 | ); 66 | } 67 | 68 | return maxDistinctStringLength; 69 | } 70 | 71 | // Test 72 | console.log(LongestSubstringWithAtMostKDistinctCharacters('araaci', 2)); // 4 73 | console.log(LongestSubstringWithAtMostKDistinctCharacters('araaci', 1)); // 2 74 | -------------------------------------------------------------------------------- /6_in-place-reversal-of-a-linked-list/61_rotate-list.js: -------------------------------------------------------------------------------- 1 | const { buildLinkedList, printLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a linked list, rotate the list to the right by k places, where k is 7 | * non-negative. 8 | * https://leetcode.com/problems/rotate-list/ 9 | * 10 | * Example 1: 11 | * Input: 1->2->3->4->5->NULL, k = 2 12 | * Output: 4->5->1->2->3->NULL 13 | * Explanation: 14 | * rotate 1 steps to the right: 5->1->2->3->4->NULL 15 | * rotate 2 steps to the right: 4->5->1->2->3->NULL 16 | * 17 | * Example 2: 18 | * Input: 0->1->2->NULL, k = 4 19 | * Output: 2->0->1->NULL 20 | * Explanation: 21 | * rotate 1 steps to the right: 2->0->1->NULL 22 | * rotate 2 steps to the right: 1->2->0->NULL 23 | * rotate 3 steps to the right: 0->1->2->NULL 24 | * rotate 4 steps to the right: 2->0->1->NULL 25 | * 26 | * Time: O(n) 27 | * Space: O(1) 28 | * 29 | * @param {ListNode} head 30 | * @param {number} k 31 | * @return {ListNode} 32 | */ 33 | function rotateLinkedList(head, k) { 34 | if (!head || k === 0) { 35 | return head; 36 | } 37 | /** 38 | * The basic idea behind this problem is: find the last node of the linked 39 | * list and connect that to the head node so the linked list become a cycle, 40 | * then find the correct node (length - k % length), cut the linked list from 41 | * the node before that one. 42 | */ 43 | let length = 0; 44 | let current = head; 45 | let prev = null; 46 | /** 47 | * O(n) get the length of the linked list, note the initial value of the `length` 48 | * is 0 because when the loop exits, `current` will be null. 49 | */ 50 | while (current) { 51 | prev = current; 52 | current = current.next; 53 | length++; 54 | } 55 | // if k is in multiples of the length, nothing happens after rotation 56 | if (k % length === 0) { 57 | return head; 58 | } 59 | 60 | // connect the last node with head node 61 | const lastNode = prev; 62 | lastNode.next = head; 63 | 64 | // find the `length - (k % length)` node and cut 65 | current = head; 66 | prev = null; 67 | let rotateTimes = length - (k % length); 68 | while (rotateTimes > 0) { 69 | prev = current; 70 | current = current.next; 71 | rotateTimes--; 72 | } 73 | prev.next = null; 74 | return current; 75 | } 76 | 77 | // Test 78 | const head1 = buildLinkedList([1, 2, 3, 4, 5]); 79 | const newHead1 = rotateLinkedList(head1, 2); 80 | printLinkedList(newHead1); // 4 -> 5 -> 1 -> 2 -> 3 81 | 82 | const head2 = buildLinkedList([0, 1, 2]); 83 | const newHead2 = rotateLinkedList(head2, 4); 84 | printLinkedList(newHead2); // 2 -> 0 -> 1 85 | -------------------------------------------------------------------------------- /11_binary-search/154_find-minimum-in-rotated-sorted-array-ii.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of numbers which is sorted in ascending order and also rotated by some 5 | * arbitrary number, find the minimum number in the array. The given array might contain 6 | * duplicates. 7 | * https://leetcode.com/problems/find-minimum-in-rotated-sorted-array-ii/ 8 | * 9 | * Example 1: 10 | * Input: [3, 3, 7, 3] 11 | * Output: 3 12 | * 13 | * Example 2: 14 | * Input: [2, 2, 2, 0, 1] 15 | * Output: 0 16 | * 17 | * 18 | * Time: O(n) in the worst case because we are doing `end--` for curtain condition, the 19 | * search range is not reduce to half. The average time complexity is O(log n) ~ O(n). 20 | * Space: 21 | * 22 | * @param {number[]} nums 23 | * @return {number} 24 | */ 25 | function findMinimumInRotatedArray(nums) { 26 | let start = 0; 27 | let end = nums.length - 1; 28 | while (start < end) { 29 | const middle = start + Math.floor((end - start) / 2); 30 | if (nums[middle] > nums[end]) { 31 | start = middle + 1; 32 | } else if (nums[middle] < nums[end]) { 33 | end = middle; 34 | } else { 35 | /** 36 | * The only difference from problem 153_find-minimum-in-rotated-sorted-array is 37 | * here: how to handle the case when `nums[middle] === nums[end]`? Think about 38 | * this examples: [3, 3, 3, 7, 3, 3] where the duplicate number itself is the 39 | * minimum one: 40 | * * in the 1st loop, `start = 0` (value 3), `end = 5` (value 3), and 41 | * `middle = 2` (value 3), since the value of `end - 1` is also 3, which means 42 | * `end` can be excluded now since `end - 1` might be the original array head 43 | * (i.e. minimum number), so what we need to do is `end--`. 44 | * * in the 2nd loop, `start = 0` (value 3), `end = 4` (value 3), and 45 | * `middle = 2` (value 3), since the value of `end - 1` is 7 which is larger than 46 | * the `end`, which means the current `end` is the original array head (i.e. 47 | * minimum number), that's why we can return `end` directly. 48 | * 49 | * Note: without this condition check here (e.g. always doing `end--` when 50 | * `nums[middle] === nums[end]`) can also return the minimum value, but the index 51 | * is not guaranteed to be the original array head. 52 | */ 53 | if (nums[end - 1] > nums[end]) { 54 | return nums[end]; 55 | } 56 | end--; 57 | } 58 | } 59 | return nums[start]; 60 | } 61 | 62 | // Test 63 | console.log(findMinimumInRotatedArray([3, 3, 7, 3])); // 3 64 | console.log(findMinimumInRotatedArray([2, 2, 2, 0, 1])); // 0 65 | -------------------------------------------------------------------------------- /9_two-heaps/480_sliding-window-median.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given an array of numbers and a number k, find the median of all the k sized 7 | * sub-arrays (or windows) of the array. 8 | * https://leetcode.com/problems/sliding-window-median/ 9 | * 10 | * Example 1: 11 | * Input: nums=[1, 2, -1, 3, 5], k = 2 12 | * Output: [1.5, 0.5, 1.0, 4.0] 13 | * Explanation: Lets consider all windows of size 2: 14 | * [1, 2, -1, 3, 5] -> median is 1.5 15 | * [1, 2, -1, 3, 5] -> median is 0.5 16 | * [1, 2, -1, 3, 5] -> median is 1.0 17 | * [1, 2, -1, 3, 5] -> median is 4.0 18 | 19 | * 20 | * Example 2: 21 | * Input: nums=[1, 2, -1, 3, 5], k = 3 22 | * Output: [1.0, 2.0, 3.0] 23 | * Explanation: Lets consider all windows of size 3: 24 | * [1, 2, -1, 3, 5] -> median is 1.0 25 | * [1, 2, -1, 3, 5] -> median is 2.0 26 | * [1, 2, -1, 3, 5] -> median is 3.0 27 | * 28 | * 29 | * Time: O(nk) 30 | * Space: O(k) <- 2 heaps 31 | * 32 | * @param {number[]} nums 33 | * @param {number} k 34 | * @return {number[]} 35 | */ 36 | function medianOfSlidingWindow(nums, k) { 37 | const smallerPart = new Heap((a, b) => a - b); 38 | const largerPart = new Heap((a, b) => b - a); 39 | 40 | const result = []; 41 | for (let i = 0; i < nums.length; i++) { 42 | // insert - O(log(k)) 43 | if (smallerPart.size() === 0 || nums[i] <= smallerPart.peek()) { 44 | smallerPart.push(nums[i]); 45 | } else { 46 | largerPart.push(nums[i]); 47 | } 48 | 49 | // re-balance - O(log(k)) 50 | _reBalanceHeaps(smallerPart, largerPart); 51 | 52 | if (i >= k - 1) { 53 | // get median 54 | if (smallerPart.size() === largerPart.size()) { 55 | result.push((smallerPart.peek() + largerPart.peek()) / 2); 56 | } else { 57 | result.push(smallerPart.peek()); 58 | } 59 | 60 | // remove - O(k) removing item requires k 61 | if (nums[i - k + 1] <= smallerPart.peek()) { 62 | smallerPart.remove(nums[i - k + 1]); 63 | } else { 64 | largerPart.remove(nums[i - k + 1]); 65 | } 66 | // re-balance - O(log(k)) 67 | _reBalanceHeaps(smallerPart, largerPart); 68 | } 69 | } 70 | return result; 71 | } 72 | 73 | function _reBalanceHeaps(smallerPart, largerPart) { 74 | if (smallerPart.size() < largerPart.size()) { 75 | smallerPart.push(largerPart.pop()); 76 | } else if (smallerPart.size() > largerPart.size() + 1) { 77 | largerPart.push(smallerPart.pop()); 78 | } 79 | } 80 | 81 | // Test 82 | console.log(medianOfSlidingWindow([1, 2, -1, 3, 5], 2)); // [1.5, 0.5, 1, 4] 83 | console.log(medianOfSlidingWindow([1, 2, -1, 3, 5], 3)); // [1, 2, 3] 84 | -------------------------------------------------------------------------------- /3_fast-slow-pointers/234_palindrome-linked-list.js: -------------------------------------------------------------------------------- 1 | const { buildLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given the head of a Singly LinkedList, write a method to check if the LinkedList 7 | * is a palindrome or not. You algorithm should use constant space and the input 8 | * LinkedList once the algorithm is finished. The algorithm should have O(N) 9 | * time complexity where N is the number of nodes in the LinkedList. 10 | * https://leetcode.com/problems/palindrome-linked-list/ 11 | * 12 | * Example 1: 13 | * Input: 2 -> 4 -> 6 -> 4 -> 2 -> null 14 | * Output: true 15 | * 16 | * Example 2: 17 | * Input: 2 -> 4 -> 6 -> 4 -> 2 -> 2 -> null 18 | * Output: false 19 | * 20 | * Time: O(n) 21 | * Space: O(1) 22 | * 23 | * @param {ListNode} head 24 | * @return {boolean} 25 | */ 26 | function isPalindromeLinkedList(head) { 27 | // return true for empty list or single node list 28 | if (!head || !head.next) { 29 | return true; 30 | } 31 | 32 | let slowPointer = head; 33 | let fastPointer = head; 34 | // find middle pointer (`slowPointer` is the middle) O(n/2) 35 | while (fastPointer && fastPointer.next) { 36 | slowPointer = slowPointer.next; 37 | fastPointer = fastPointer.next.next; 38 | } 39 | // reverse second half of the linked list O(n/2) 40 | let prev = null; 41 | let current = slowPointer; 42 | while (current) { 43 | const next = current.next; 44 | current.next = prev; 45 | prev = current; 46 | current = next; 47 | } 48 | let result = true; 49 | // pointer 1: start from beginning 50 | let startPointer = head; 51 | // pointer 2: start from last node (`prev` is the last node now after reverse) 52 | let endPointer = prev; 53 | // O(n/2) 54 | while (startPointer && endPointer) { 55 | if (startPointer.val !== endPointer.val) { 56 | result = false; 57 | /** 58 | * We can't return false directly here, because we need to revert the right 59 | * half back as the problem description requires (keep linked list as original) 60 | */ 61 | break; 62 | } 63 | startPointer = startPointer.next; 64 | endPointer = endPointer.next; 65 | } 66 | // reverse second half back O(2/n) 67 | current = prev; // last node 68 | prev = null; 69 | while (current) { 70 | const next = current.next; 71 | current.next = prev; 72 | prev = current; 73 | current = next; 74 | } 75 | 76 | return result; 77 | } 78 | 79 | // Test 80 | const head1 = buildLinkedList([2, 4, 6, 6, 4]); 81 | console.log(isPalindromeLinkedList(head1)); // false 82 | 83 | const head2 = buildLinkedList([2, 4, 6, 6, 4, 2]); 84 | console.log(isPalindromeLinkedList(head2)); // true 85 | -------------------------------------------------------------------------------- /8_DFS/129_sum-root-to-leaf-numbers.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree where each node can only have a digit (0-9) value, each 7 | * root-to-leaf path will represent a number. Find the total sum of all the numbers 8 | * represented by all paths. 9 | * https://leetcode.com/problems/sum-root-to-leaf-numbers/ 10 | * 11 | * Example 1: 12 | * Input: [1, 7, 9, null, null, 2, 9] 13 | * Output: 408 14 | * Explanation: The sum of all path numbers: 17 + 192 + 199 15 | * 16 | * Example 2: 17 | * Input: [1, 0, 1, null, 1, 6, 5] 18 | * Output: 332 19 | * Explanation: The sum of all path numbers: 101 + 116 + 115 20 | * 21 | * 22 | * Time: O(n) 23 | * Space: O(h) <- recursion stack 24 | * 25 | * @param {TreeNode} root 26 | * @return {number} 27 | */ 28 | function sumOfRootToLeafNumbers(root) { 29 | return _traverse(root, 0); 30 | } 31 | 32 | function _traverse(node, curSum) { 33 | if (!node) { 34 | return 0; 35 | } 36 | /** 37 | * Similar as 113_path-sum-ii, the only differences is here the `doVisit` is to 38 | * treat the path as a number. 39 | */ 40 | curSum = curSum * 10 + node.val; 41 | if (!node.left && !node.right) { 42 | return curSum; 43 | } 44 | /** 45 | * What's the return value of the `_traverse` here? 46 | * 47 | * For each node passed in, we get `leftValue/rightValue` at the end, so at that 48 | * moment we must finish visiting the node, so the very first time when we have 49 | * both `leftValue` and `rightValue`, it just finishes visiting the left child and 50 | * the right child of a level (h - 1) node, so the sum will be the two path numbers 51 | * which passes that node. For example, the example 1 tree above, the first node 52 | * being fully visited (both left/right children has been visited) is the node 9, 53 | * at that moment, leftValue=192, rightValue=199. 54 | * 55 | * Note: utilise the return value of the recursion can prevent creating unnecessary 56 | * global variables, for example, if we ignore the return value, we need to maintain 57 | * a `totalSum` and add number to that variable every time we reach the leaf node. 58 | */ 59 | const leftValue = _traverse(node.left, curSum); 60 | // console.log('after left:', leftValue); 61 | const rightValue = _traverse(node.right, curSum); 62 | // console.log('after right:', leftValue, rightValue); 63 | return leftValue + rightValue; 64 | } 65 | 66 | // Test 67 | const tree1 = buildTreeBFS([1, 7, 9, null, null, 2, 9]); 68 | console.log(sumOfRootToLeafNumbers(tree1)); // 408 69 | 70 | const tree2 = buildTreeBFS([1, 0, 1, null, 1, 6, 5]); 71 | console.log(sumOfRootToLeafNumbers(tree2)); // 332 72 | -------------------------------------------------------------------------------- /2_two-pointers/75_sort-colors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array containing 0s, 1s and 2s, sort the array in-place. You should treat 5 | * numbers of the array as objects, hence, we can’t count 0s, 1s, and 2s to recreate the 6 | * array. The flag of the Netherlands consists of three colors: red, white and blue; and 7 | * since our input array also consists of three different numbers that is why it is also 8 | * called Dutch National Flag problem. 9 | * https://leetcode.com/problems/sort-colors/ 10 | * 11 | * Example 1: 12 | * Input: [1, 0, 2, 1, 0] 13 | * Output: [0 0 1 1 2] 14 | * 15 | * Example 2: 16 | * Input: [2, 2, 0, 1, 2, 0] 17 | * Output: [0 0 1 2 2 2] 18 | * 19 | * Time: O(n) 20 | * Space: O(1) 21 | * 22 | * @param {number[]} colorArray 23 | * @return {number[]} 24 | */ 25 | function sortColors(colorArray) { 26 | // pointer 1: next available index for 0 27 | let nextIndexForZero = 0; 28 | // pointer 2: next available index for 2 29 | let nextIndexForTwo = colorArray.length - 1; 30 | /** 31 | * The target is to make sure: 32 | * [0, nextIndexForZero] => 0 33 | * (nextIndexForZero, nextIndexForTwo) => 1 34 | * [nextIndexForTwo, ...] => 2 35 | * By default, we consider all numbers occupied 1's position, the the loop variable i 36 | * here is correct if its value is 1. 37 | */ 38 | let i = 0; 39 | while (i <= nextIndexForTwo) { 40 | if (colorArray[i] === 0) { 41 | /** 42 | * For 0, swap it to the next available index, then it's safe to do `i++` because 43 | * we have (i >= nextIndexForZero), the number on `nextIndexForZero` is processed 44 | * in the previous loop already, so it can't be 2. 45 | */ 46 | _swap(colorArray, i, nextIndexForZero); 47 | nextIndexForZero++; 48 | i++; 49 | } else if (colorArray[i] === 2) { 50 | /** 51 | * For 2, swap it to the next available index, then we shouldn't do `i++` because 52 | * the number on `nextIndexForTwo` is not processed, can be 0 possibly. 53 | */ 54 | _swap(colorArray, i, nextIndexForTwo); 55 | nextIndexForTwo--; 56 | } else { 57 | /** 58 | * For 1, since 1 should be in the position of [nextIndexForZero, nextIndexForTwo], 59 | * we already know (i >= nextIndexForZero) above and the while loop will enforce 60 | * (i <= nextIndexForTow), so it's okay to do `i++` without any swapping. 61 | */ 62 | i++; 63 | } 64 | } 65 | 66 | return colorArray; 67 | } 68 | 69 | function _swap(array, i, j) { 70 | [array[i], array[j]] = [array[j], array[i]]; 71 | } 72 | 73 | // Test 74 | console.log(sortColors([1, 0, 2, 1, 0])); // [0 0 1 1 2] 75 | console.log(sortColors([2, 2, 0, 1, 2, 0])); // [0 0 1 2 2 2] 76 | -------------------------------------------------------------------------------- /4_merge-intervals/986_interval-list-intersections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given two lists of intervals, find the intersection of these two lists. Each list 5 | * consists of disjoint intervals sorted on their start time. 6 | * https://leetcode.com/problems/interval-list-intersections/ 7 | * 8 | * Example 1: 9 | * Input: arr1=[[1, 3], [5, 6], [7, 9]], arr2=[[2, 3], [5, 7]] 10 | * Output: [2, 3], [5, 6], [7, 7] 11 | * Explanation: The output list contains the common intervals between the two lists. 12 | * 13 | * Example 2: 14 | * Input: arr1=[[1, 3], [5, 7], [9, 12]], arr2=[[5, 10]] 15 | * Output: [5, 7], [9, 10] 16 | * Explanation: The output list contains the common intervals between the two lists. 17 | * 18 | * Time: O(m + n) 19 | * Space: O(1) 20 | * 21 | * @param {number[][]} intervals1 22 | * @param {number[][]} intervals2 23 | * @return {number[][]} 24 | */ 25 | function intervalIntersection(intervals1, intervals2) { 26 | const result = []; 27 | let i = 0; 28 | let j = 0; 29 | while (i < intervals1.length && j < intervals2.length) { 30 | /** 31 | * This condition here is equivalent to: 32 | * !( intervals1[i][1] < intervals2[j][0] || intervals2[j][1] < intervals1[i][0] ) 33 | * which is, `not (two intervals do not overlap)` => "two intervals overlap" 34 | */ 35 | if ( 36 | intervals1[i][1] >= intervals2[j][0] && 37 | intervals2[j][1] >= intervals1[i][0] 38 | ) { 39 | result.push([ 40 | Math.max(intervals1[i][0], intervals2[j][0]), 41 | Math.min(intervals1[i][1], intervals2[j][1]), 42 | ]); 43 | } 44 | /** 45 | * Regardless of overlap/not overlap, we can always move on to next one, the 46 | * strategy here is to move the one which finishes earlier (smaller end), because 47 | * the other one (larger end) is still possible to overlap with next one. 48 | * 49 | * If the two intervals do not overlap, this move strategy here is to skip the 50 | * intervals which are not possible to overlap with others, e.g. 51 | * intervals1[i] -> [1, 2], intervals2[j] -> [5, 6], intervals1[i] needs to be 52 | * skipped. 53 | */ 54 | if (intervals1[i][1] < intervals2[j][1]) { 55 | i++; 56 | } else { 57 | j++; 58 | } 59 | } 60 | return result; 61 | } 62 | 63 | const result1 = intervalIntersection( 64 | [ 65 | [1, 3], 66 | [5, 6], 67 | [7, 9], 68 | ], 69 | [ 70 | [2, 3], 71 | [5, 7], 72 | ] 73 | ); 74 | result1.forEach((i) => console.log(i)); // [[2, 3], [5, 6], [7, 7]] 75 | const result2 = intervalIntersection( 76 | [ 77 | [1, 3], 78 | [5, 7], 79 | [9, 12], 80 | ], 81 | [[5, 10]] 82 | ); 83 | result2.forEach((i) => console.log(i)); // [[5, 7], [9, 10]] 84 | -------------------------------------------------------------------------------- /8_DFS/113_path-sum-ii.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree and a number S, find all paths from root-to-leaf such that 7 | * the sum of all the node values of each path equals S. 8 | * https://leetcode.com/problems/path-sum-ii/ 9 | * 10 | * Example 1: 11 | * Input: [1, 7, 9, 4, 5, 2, 7], 12 12 | * Output: [ [1, 7, 4], [1, 9, 2] ] 13 | * 14 | * Example 2: 15 | * Input: [12, 7, 1, null, 4, 10, 5], 23 16 | * Output: [ [12, 7, 4], [12, 1, 10] ] 17 | * 18 | * 19 | * Time: O(n^2) <- for each leaf, we might call `concat()`, which is a O(h) 20 | * Space: O(h) (log(n) ~ n) <- `curPath` variable, without considering the 21 | * return value space 22 | * 23 | * @param {TreeNode} root 24 | * @param {number} sum 25 | * @return {number[][]} 26 | */ 27 | function getAllPathsWithSum(root, sum) { 28 | const result = []; 29 | _traverse(root, sum, [], result); 30 | return result; 31 | } 32 | 33 | function _traverse(node, targetSum, curPath, result) { 34 | if (!node) { 35 | return; 36 | } 37 | if (!node.left && !node.right && targetSum === node.val) { 38 | /** 39 | * We reuse `curPath` to reduce the space complexity, so only one array 40 | * is created for the whole traversal, that's why we need to copy that 41 | * before pushing to the final result, otherwise it will be modified by 42 | * the following traversal. 43 | * 44 | * Note: `concat` is a O(n) operation, because it needs to copy all the 45 | * items inside. 46 | */ 47 | result.push(curPath.concat(node.val)); 48 | return; 49 | } 50 | /** 51 | * Similar as 112_path-sum, the differences here are: 52 | * 1. we need to find all the paths, so no `||` below because we need to 53 | * traverse all the nodes 54 | * 2. we need to save all the visited paths, so another variable `curPath` 55 | * is created here. 56 | * 57 | * The `doVisit` here is to put the current node value into the `curPath`, 58 | * since `curPath` is an object (array), so at the end of the current node 59 | * traversal, we need to revert the change, that's why we need the `pop()` 60 | * at the end. 61 | */ 62 | curPath.push(node.val); 63 | _traverse(node.left, targetSum - node.val, curPath, result); 64 | _traverse(node.right, targetSum - node.val, curPath, result); 65 | curPath.pop(); 66 | } 67 | 68 | // Test 69 | const tree1 = buildTreeBFS([1, 7, 9, 4, 5, 2, 7]); 70 | const result1 = getAllPathsWithSum(tree1, 12); 71 | result1.forEach((i) => console.log(i)); // [ [1, 7, 4], [1, 9, 2] ] 72 | 73 | const tree2 = buildTreeBFS([12, 7, 1, null, 4, 10, 5]); 74 | const result2 = getAllPathsWithSum(tree2, 23); 75 | result2.forEach((i) => console.log(i)); // [ [12, 7, 4], [12, 1, 10] ] 76 | -------------------------------------------------------------------------------- /12_bitwise-xor/260_single-number-iii.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * In a non-empty array of numbers, every number appears exactly twice except two 5 | * numbers that appear only once. Find the two numbers that appear only once. 6 | * https://leetcode.com/problems/single-number-iii/ 7 | * 8 | * Example 1: 9 | * Input: [1, 4, 2, 1, 3, 5, 6, 2, 3, 5] 10 | * Output: [4, 6] 11 | * 12 | * Example 2: 13 | * Input: [2, 1, 3, 2] 14 | * Output: [1, 3] 15 | * 16 | * 17 | * Time: O(n) 18 | * Space: O(1) 19 | * 20 | * @param {number[]} nums 21 | * @return {number[]} 22 | */ 23 | function findTwoSingleNumbers(nums) { 24 | /** 25 | * The main idea here is: after XOR all the numbers in the array, what we get is 26 | * the result of `singleNumber1 ^ singleNumber2`, so how to get the actual numbers 27 | * from this XOR result? Think about how XOR works, if a result bit is 1, that bit 28 | * for one number must be 1, and for the other number must be 0. We can utilise this 29 | * property: 30 | * * first find a non-zero bit in the XOR result 31 | * * use all numbers from the array to bitwise and (&) with this bit, obviously there 32 | * can only be 2 results: 0 or the bit itself, the array will be split in 2 groups, 33 | * the 2 numbers we are trying to search fall in each group (because for that exact 34 | * bit, one number has 1 and the other has 0) 35 | * * XOR all numbers for each group, since in each group all numbers appear twice 36 | * except the target number, the XOR result for each group is what we are looking for. 37 | */ 38 | let xorForTwoNumbers = 0; 39 | for (let i = 0; i < nums.length; i++) { 40 | xorForTwoNumbers ^= nums[i]; 41 | } 42 | /** 43 | * This is a handy way to find the rightmost set bit (the 1st non-zero bit from right 44 | * side), why? Negative number are represented by 2's complement (invert each bit then 45 | * plus 1), for example: 46 | * -6 -> 110 (6) -> 001 + 1 -> 010, then 6 & -6 -> 110 & 010 -> 010 -> 4 47 | * so 4 is the rightmost set bit for 6. 48 | * 49 | * Another naive method is to use while loop to find this: 50 | * let bitMask = 1; 51 | * while ((xor & bitMask) === 0) { 52 | * bitMask = bitMask << 1; 53 | * } 54 | */ 55 | const rightmostSetBit = xorForTwoNumbers & -xorForTwoNumbers; 56 | let xorWithSetBit0 = 0; 57 | let xorWithSetBit1 = 0; 58 | for (let i = 0; i < nums.length; i++) { 59 | if ((nums[i] & rightmostSetBit) === rightmostSetBit) { 60 | xorWithSetBit1 ^= nums[i]; 61 | } else { 62 | xorWithSetBit0 ^= nums[i]; 63 | } 64 | } 65 | return [xorWithSetBit0, xorWithSetBit1]; 66 | } 67 | 68 | // Test 69 | console.log(findTwoSingleNumbers([1, 4, 2, 1, 3, 5, 6, 2, 3, 5])); // [4, 6] 70 | console.log(findTwoSingleNumbers([2, 1, 3, 2])); // [1, 3] 71 | -------------------------------------------------------------------------------- /1_sliding-window/438_find-all-anagrams-in-a-string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a string and a pattern, find all anagrams of the pattern in the given string. 5 | * "Anagram" is actually a Permutation of a string. Write a function to return a list of 6 | * starting indices of the anagrams of the pattern in the given string. 7 | * https://leetcode.com/problems/find-all-anagrams-in-a-string/ 8 | * 9 | * Example 1: 10 | * Input: String="ppqp", Pattern="pq" 11 | * Output: [1, 2] 12 | * Explanation: The two anagrams of the pattern in the given string are "pq" and "qp". 13 | * 14 | * Example 2: 15 | * Input: String="abbcabc", Pattern="abc" 16 | * Output: [2, 3, 4] 17 | * Explanation: The three anagrams of the pattern in the given string are "bca", "cab", and 18 | * "abc". 19 | * 20 | * Time: O(n + m) 21 | * Space: O(m) 22 | * 23 | * Note: exactly the same as 567_permutation-in-string.js, see detail explanations there, the 24 | * only difference is for this problem we need to record the `windowStart` when there's a 25 | * full match, and continue the loop to find all full matches. 26 | * 27 | * @param {string} string 28 | * @param {string} pattern 29 | * @return {number[]} 30 | */ 31 | function findAllAnagramsInString(string, pattern) { 32 | const charCountMapInPattern = {}; 33 | for (let i = 0; i < pattern.length; i++) { 34 | charCountMapInPattern[pattern[i]] = 35 | (charCountMapInPattern[pattern[i]] || 0) + 1; 36 | } 37 | const expectedMatchCount = Object.keys(charCountMapInPattern).length; 38 | 39 | let windowStart = 0; 40 | let windowEnd = 0; 41 | 42 | let matchedCharCount = 0; 43 | const resultIndices = []; 44 | 45 | const charCountMapInWindow = {}; 46 | while (windowEnd < string.length) { 47 | const endChar = string[windowEnd]; 48 | if (endChar in charCountMapInPattern) { 49 | charCountMapInWindow[endChar] = (charCountMapInWindow[endChar] || 0) + 1; 50 | if (charCountMapInWindow[endChar] === charCountMapInPattern[endChar]) { 51 | matchedCharCount++; 52 | } 53 | } 54 | windowEnd++; 55 | 56 | if (windowEnd >= pattern.length) { 57 | if (matchedCharCount === expectedMatchCount) { 58 | resultIndices.push(windowStart); 59 | } 60 | const startChar = string[windowStart]; 61 | if (startChar in charCountMapInPattern) { 62 | if ( 63 | charCountMapInWindow[startChar] === charCountMapInPattern[startChar] 64 | ) { 65 | matchedCharCount--; 66 | } 67 | charCountMapInWindow[startChar]--; 68 | } 69 | windowStart++; 70 | } 71 | } 72 | 73 | return resultIndices; 74 | } 75 | 76 | // Test 77 | console.log(findAllAnagramsInString('ppqp', 'pq')); // [1, 2] 78 | console.log(findAllAnagramsInString('abbcabc', 'abc')); // [2, 3, 4] 79 | -------------------------------------------------------------------------------- /1_sliding-window/904_fruit-into-baskets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of characters where each character represents a fruit tree, you are given 5 | * two baskets and your goal is to put maximum number of fruits in each basket. The only 6 | * restriction is that each basket can have only one type of fruit. 7 | * https://leetcode.com/problems/fruit-into-baskets/ 8 | * 9 | * Example 1: 10 | * Input: Fruit=['A', 'B', 'C', 'A', 'C'] 11 | * Output: 3 12 | * Explanation: We can put 2 'C' in one basket and one 'A' in the other from the subarray 13 | * ['C', 'A', 'C'] 14 | * 15 | * Example 2: 16 | * Input: Fruit=['A', 'B', 'C', 'B', 'B', 'C'] 17 | * Output: 5 18 | * Explanation: We can put 3 'B' in one basket and two 'C' in the other basket. This can be 19 | * done if we start with the second letter: ['B', 'C', 'B', 'B', 'C'] 20 | * 21 | * Time: O(n) <- O(n + n) 22 | * Space: O(n) <- map 23 | * 24 | * @param {string[]} fruitTrees 25 | * @return {number} 26 | */ 27 | function fruitIntoBaskets(fruitTrees) { 28 | const buckets = 2; 29 | 30 | let windowStart = 0; 31 | let windowEnd = 0; 32 | 33 | let fruitTypeCountMapInWindow = {}; 34 | let maxFruitCount = 0; 35 | 36 | // O(n) -> `windowEnd` run through all fruit types in the `fruitTrees` 37 | while (windowEnd < fruitTrees.length) { 38 | const endFruitType = fruitTrees[windowEnd]; 39 | // Aggregation is the count for each fruit type in the window 40 | fruitTypeCountMapInWindow[endFruitType] = 41 | (fruitTypeCountMapInWindow[endFruitType] || 0) + 1; 42 | windowEnd++; 43 | 44 | /** 45 | * Keep shrinking the window if the fruit type in the window (key length of 46 | * `fruitTypeCountMapInWindow`) is larger than k. 47 | * 48 | * Note: 49 | * * O(n) in total -> `windowStart` run through all fruits in the `fruitTrees` for all 50 | * the outer loops 51 | */ 52 | while (Object.keys(fruitTypeCountMapInWindow).length > buckets) { 53 | const startFruitType = fruitTrees[windowStart]; 54 | fruitTypeCountMapInWindow[startFruitType]--; 55 | if (fruitTypeCountMapInWindow[startFruitType] === 0) { 56 | delete fruitTypeCountMapInWindow[startFruitType]; 57 | } 58 | windowStart++; 59 | } 60 | /** 61 | * Even the `fruitTypeCountMapInWindow` key length is less than `buckets`, we still need 62 | * to return the result, because bucket might not be used 63 | * 64 | * Note: `windowEnd - windowStart` because `windowEnd` is not in window yet 65 | */ 66 | maxFruitCount = Math.max(maxFruitCount, windowEnd - windowStart); 67 | } 68 | 69 | return maxFruitCount; 70 | } 71 | 72 | // Test 73 | console.log(fruitIntoBaskets(['A', 'B', 'C', 'A', 'C'])); // 3 74 | console.log(fruitIntoBaskets(['A', 'B', 'C', 'B', 'B', 'C'])); // 5 75 | -------------------------------------------------------------------------------- /5_cyclic-sort/0_find-first-k-missing-positive-numbers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an unsorted array containing numbers and a number ‘k’, find the first ‘k’ 5 | * missing positive numbers in the array. 6 | * 7 | * Example 1: 8 | * Input: [3, -1, 4, 5, 5], k=3 9 | * Output: [1, 2, 6] 10 | * Explanation: The smallest missing positive numbers are 1, 2 and 6. 11 | * 12 | * Example 2: 13 | * Input: [2, 3, 4], k=3 14 | * Output: [1, 5, 6] 15 | * Explanation: The smallest missing positive numbers are 1, 5 and 6. 16 | * 17 | * Time: O(n + k) 18 | * Space: O(k) <- Set 19 | * 20 | * @param {number[]} nums 21 | * @param {number} k 22 | * @return {number[]} 23 | */ 24 | function findFirstKMissingPositiveNumbers(nums, k) { 25 | const n = nums.length; 26 | // Similar as 41_first-smallest-missing-positive for this part 27 | for (let i = 0; i < n; i++) { 28 | while (nums[i] !== i + 1) { 29 | if (nums[i] >= 1 && nums[i] <= n && nums[i] !== nums[nums[i] - 1]) { 30 | _swap(nums, i, nums[i] - 1); 31 | } else { 32 | break; 33 | } 34 | } 35 | } 36 | const result = []; 37 | /** 38 | * We need `extraNumbers` to store the "invalid" numbers in the array because 39 | * these invalid numbers can be the missing positives. Also note here we only 40 | * push to the `result` when the length is less than k. 41 | */ 42 | const extraNumbers = new Set(); 43 | for (let i = 0; i < nums.length; i++) { 44 | if (result.length < k) { 45 | if (nums[i] !== i + 1) { 46 | extraNumbers.add(nums[i]); 47 | result.push(i + 1); 48 | } 49 | } else { 50 | // if `result` already has k items, we are good to go, return it directly 51 | return result; 52 | } 53 | } 54 | /** 55 | * If the result length is still less than k, the missing positive will be 56 | * [n + 1, ...], we start with n+1 and check if the missing number is already 57 | * inside the `extraNumbers`, if so, we skip it. 58 | * 59 | * Time: O(k) because the length of `extraNumbers` and `result` are the same 60 | * according to line 50-51 above, the nested loop below can only run 61 | * `k - result.length` (outer while) + `extraNumber.size` (inner while), 62 | * which is exactly k times. 63 | */ 64 | let nextMissingNumber = n + 1; 65 | while (result.length < k) { 66 | while (extraNumbers.has(nextMissingNumber)) { 67 | nextMissingNumber++; 68 | } 69 | result.push(nextMissingNumber); 70 | nextMissingNumber++; 71 | } 72 | return result; 73 | } 74 | 75 | function _swap(array, i, j) { 76 | [array[i], array[j]] = [array[j], array[i]]; 77 | } 78 | 79 | // Test 80 | console.log(findFirstKMissingPositiveNumbers([3, -1, 4, 5, 5], 3)); // [1, 2, 6] 81 | console.log(findFirstKMissingPositiveNumbers([2, 3, 4], 3)); // [1, 5, 6] 82 | -------------------------------------------------------------------------------- /1_sliding-window/713_subarray-product-less-than-k.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array with positive numbers and a target number, find all of its contiguous 5 | * subarrays whose product is less than the target number, output the count. 6 | * https://leetcode.com/problems/subarray-product-less-than-k/ 7 | * 8 | * Example 1: 9 | * Input: [2, 5, 3, 10], k=30 10 | * Output: 6 11 | * Explanation: There are six contiguous subarrays [2], [5], [2, 5], [3], [5, 3], [10] 12 | * whose product is less than the k. 13 | * 14 | * Example 2: 15 | * Input: [8, 2, 6, 5], k=50 16 | * Output: 7 17 | * Explanation: There are seven contiguous subarrays [8], [2], [8, 2], [6], [2, 6], [5], 18 | * [6, 5] whose product is less than the k. 19 | * 20 | * Time: O(n) 21 | * Space: O(1) 22 | * 23 | * 24 | * @param {number[]} array 25 | * @param {number} k 26 | * @return {number} 27 | */ 28 | function subarrayProductLessThanK(array, k) { 29 | let windowStart = 0; 30 | let windowEnd = 0; 31 | 32 | let windowProduct = 1; 33 | let count = 0; 34 | 35 | // O(n) <- `windowEnd` will run through all numbers in the array 36 | while (windowEnd < array.length) { 37 | windowProduct *= array[windowEnd]; 38 | windowEnd++; 39 | 40 | /** 41 | * Shrink the window when the product is larger than k, we put 42 | * `windowStart < windowEnd` cause we need to make sure there's at least one number 43 | * in the window (remember `windowEnd` is not inside the window yet). 44 | * 45 | * Time: O(n) in total -> `windowStart` run through all the number for all the outer 46 | * loop. 47 | */ 48 | while (windowProduct >= k && windowStart < windowEnd) { 49 | windowProduct /= array[windowStart]; 50 | windowStart++; 51 | } 52 | /** 53 | * Every time a valid window is found, we need to check how many subarrays inside the 54 | * window, which is actually the widow size, For example: 55 | * 1. for cases when a new number is added to the window 56 | * previous window: [1, 2] (current subarrays [1] [2] [1, 2]) 57 | * current window: [1, 2, 3], 3 is newly added, then the new subarrays are [3] [2, 3] 58 | * [1, 2, 3] 59 | * 2. for cases when a existing number is removed from the window 60 | * previous window: [1, 2, 5] (5 is the newly added number which breaks) 61 | * current window: [2, 5], 1 is removed, then the new subarrays are [5], [5, 2] 62 | * 63 | * Actually, to calculate the count of the newly added subarrays, we always start from 64 | * the `windowEnd`, and `unshift` one number before it until we reach `windowStart`. 65 | */ 66 | count += windowEnd - windowStart; 67 | } 68 | 69 | return count; 70 | } 71 | 72 | // Test 73 | console.log(subarrayProductLessThanK([2, 5, 3, 10], 30)); // 6 74 | console.log(subarrayProductLessThanK([8, 2, 6, 5], 50)); // 7 75 | -------------------------------------------------------------------------------- /1_sliding-window/30_substring-with-concatenation-of-all-words.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a string and a list of words, find all the starting indices of substrings in the 5 | * given string that are a concatenation of all the given words exactly once without any 6 | * overlapping of words. It is given that all words are of the same length. 7 | * https://leetcode.com/problems/substring-with-concatenation-of-all-words/ 8 | * 9 | * Example 1: 10 | * Input: String="catfoxcat", Words=["cat", "fox"] 11 | * Output: [0, 3] 12 | * Explanation: The two substring containing both the words are "catfox" & "foxcat". 13 | * 14 | * Example 2: 15 | * Input: String="catcatfoxfox", Words=["cat", "fox"] 16 | * Output: [3] 17 | * Explanation: The only substring containing both the words is "catfox". 18 | * 19 | * Time: O(m * n * len) 20 | * Space: O(m + len) <- `substr` creates a new string which will cost `len` 21 | * 22 | * Not: the approach we used here is not related to sliding window technically, because 23 | * we didn't pick up the result of the current move in the next move, e.g. every loop we 24 | * create a new `visitedWordCountMap` (aggregation). 25 | * 26 | * @param {string} string 27 | * @param {string[]} words 28 | * @return {number[]} 29 | */ 30 | function substringWithConcatenationOfAllWords(string, words) { 31 | const wordLength = words[0].length; 32 | const wordCountMap = {}; 33 | // O(m) 34 | for (let i = 0; i < words.length; i++) { 35 | wordCountMap[words[i]] = (wordCountMap[words[i]] || 0) + 1; 36 | } 37 | 38 | const lastIndex = string.length - wordLength * words.length; 39 | const resultIndices = []; 40 | 41 | // O(n - m * len) 42 | for (let startIndex = 0; startIndex <= lastIndex; startIndex++) { 43 | const visitedWordCountMap = {}; 44 | // O(m) 45 | for (let i = 0; i < words.length; i++) { 46 | // O(len) 47 | const word = string.substr(startIndex + i * wordLength, wordLength); 48 | // exit loop immediately if word is not existed 49 | if (!(word in wordCountMap)) { 50 | break; 51 | } 52 | visitedWordCountMap[word] = (visitedWordCountMap[word] || 0) + 1; 53 | // exit loop immediately if word count is larger than expected 54 | if (visitedWordCountMap[word] > wordCountMap[word]) { 55 | break; 56 | } 57 | 58 | /** 59 | * Every word can find a match and the for loop finished without breaking, 60 | * it's guaranteed that this is a valid start index. 61 | */ 62 | if (i === words.length - 1) { 63 | resultIndices.push(startIndex); 64 | } 65 | } 66 | } 67 | 68 | return resultIndices; 69 | } 70 | 71 | // Test 72 | console.log(substringWithConcatenationOfAllWords('catfoxcat', ['cat', 'fox'])); // [0, 3] 73 | console.log( 74 | substringWithConcatenationOfAllWords('catcatfoxfox', ['cat', 'fox']) 75 | ); // [3] 76 | -------------------------------------------------------------------------------- /2_two-pointers/18_4sum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of unsorted numbers and a target number, find all unique quadruplets in 5 | * it, whose sum is equal to the target number. Notice that the solution set must not 6 | * contain duplicate quadruplets. 7 | * https://leetcode.com/problems/4sum/ 8 | * 9 | * Example 1: 10 | * Input: [4, 1, 2, -1, 1, -3], target=1 11 | * Output: [[-3, -1, 1, 4], [-3, 1, 1, 2]] 12 | * Explanation: Both the quadruplets add up to the target. 13 | * 14 | * Example 2: 15 | * Input: [2, 0, -1, 1, -2, 2], target=2 16 | * Output: [[-2, 0, 2, 2], [-1, 0, 1, 2]] 17 | * Explanation: Both the quadruplets add up to the target. 18 | * 19 | * Time: O(n^3) <- O(nlog(n)) + O(n^3) 20 | * Space: O(n) <- merge sort 21 | * 22 | * Note: similar as 15_3sum.js, check the detailed explanation there. 23 | * 24 | * @param {number[]} array 25 | * @param {number} targetSum 26 | * @return {number[][]} 27 | */ 28 | function quadrupleSumToTarget(array, targetSum) { 29 | // O(nlog(n)) 30 | array.sort((a, b) => a - b); 31 | 32 | const result = []; 33 | 34 | // O(n) 35 | for (let i = 0; i <= array.length - 4; i++) { 36 | if (i > 0 && array[i] === array[i - 1]) { 37 | continue; 38 | } 39 | 40 | // O(n) 41 | for (let j = i + 1; j <= array.length - 3; j++) { 42 | /** 43 | * Can't use `j > 0` here, because j is always larger than 0, what we want to 44 | * skip is when j is at least at its 2nd position (1st position is i + 1) 45 | */ 46 | if (j > i + 1 && array[j] === array[j - 1]) { 47 | continue; 48 | } 49 | 50 | let left = j + 1; 51 | let right = array.length - 1; 52 | 53 | // O(n) 54 | while (left < right) { 55 | const num1 = array[left]; 56 | const num2 = array[right]; 57 | const sum = array[i] + array[j] + num1 + num2; 58 | if (sum === targetSum) { 59 | result.push([array[i], array[j], array[left], array[right]]); 60 | left++; 61 | right--; 62 | while (left < right && array[left] === num1) { 63 | left++; 64 | } 65 | while (left < right && array[right] === num2) { 66 | right--; 67 | } 68 | } else if (sum < targetSum) { 69 | while (left < right && array[left] === num1) { 70 | left++; 71 | } 72 | } else { 73 | while (left < right && array[right] === num2) { 74 | right--; 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | return result; 82 | } 83 | 84 | // Test 85 | const result1 = quadrupleSumToTarget([4, 1, 2, -1, 1, -3], 1); 86 | // [[-3, -1, 1, 4], [-3, 1, 1, 2]] 87 | result1.forEach((i) => console.log(i)); 88 | const result2 = quadrupleSumToTarget([2, 0, -1, 1, -2, 2], 2); 89 | // [[-2, 0, 2, 2], [-1, 0, 1, 2]] 90 | result2.forEach((i) => console.log(i)); 91 | -------------------------------------------------------------------------------- /1_sliding-window/76_minimum-window-substring.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a string and a pattern, find the smallest substring in the given string which has 5 | * all the characters of the given pattern. 6 | * https://leetcode.com/problems/minimum-window-substring/ 7 | * 8 | * Example 1: 9 | * Input: String="aabdec", Pattern="abc" 10 | * Output: "abdec" 11 | * Explanation: The smallest substring having all characters of the pattern is "abdec" 12 | * 13 | * Example 2: 14 | * Input: String="abdbca", Pattern="abc" 15 | * Output: "bca" 16 | * Explanation: The smallest substring having all characters of the pattern is "bca". 17 | * 18 | * Time: O(n) <- O(m + n + n + n) 19 | * Space: O(m) <- map 20 | * 21 | * @param {string} string 22 | * @param {string} pattern 23 | * @return {string} 24 | */ 25 | function minimumWindowSubstring(string, pattern) { 26 | const charCountMapInPattern = {}; 27 | // O(m) 28 | for (let i = 0; i < pattern.length; i++) { 29 | charCountMapInPattern[pattern[i]] = 30 | (charCountMapInPattern[pattern[i]] || 0) + 1; 31 | } 32 | const expectedMatchCount = Object.keys(charCountMapInPattern).length; 33 | 34 | let windowStart = 0; 35 | let windowEnd = 0; 36 | 37 | let matchedCharCount = 0; 38 | let minWindowLength = string.length + 1; 39 | let minWindowStart; 40 | 41 | // O(n) `windowEnd` run through all the characters in the `string` 42 | while (windowEnd < string.length) { 43 | const endChar = string[windowEnd]; 44 | windowEnd++; 45 | if (endChar in charCountMapInPattern) { 46 | charCountMapInPattern[endChar]--; 47 | if (charCountMapInPattern[endChar] === 0) { 48 | matchedCharCount++; 49 | } 50 | } 51 | 52 | /** 53 | * Keep shrinking the window if the substring in the window can cover the pattern, 54 | * during shrinking, we try to find the minimum length of the substring. 55 | * 56 | * O(n) in total -> `windowStart` run through all the characters in the `string` for 57 | * all the outer loop 58 | */ 59 | while (matchedCharCount === expectedMatchCount) { 60 | if (windowEnd - windowStart < minWindowLength) { 61 | minWindowLength = windowEnd - windowStart; 62 | minWindowStart = windowStart; 63 | } 64 | 65 | const startChar = string[windowStart]; 66 | windowStart++; 67 | if (startChar in charCountMapInPattern) { 68 | if (charCountMapInPattern[startChar] === 0) { 69 | matchedCharCount--; 70 | } 71 | charCountMapInPattern[startChar]++; 72 | } 73 | } 74 | } 75 | 76 | if (minWindowLength === string.length + 1) { 77 | return ''; 78 | } 79 | 80 | // O(n) 81 | return string.slice(minWindowStart, minWindowStart + minWindowLength); 82 | } 83 | 84 | // Test 85 | console.log(minimumWindowSubstring('aabdec', 'abc')); // 'abdec' 86 | console.log(minimumWindowSubstring('abdbca', 'abc')); // 'bca' 87 | -------------------------------------------------------------------------------- /2_two-pointers/581_shortest-unsorted-continuous-subarray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array, find the length of the smallest subarray in it which when sorted 5 | * will sort the whole array. 6 | * https://leetcode.com/problems/shortest-unsorted-continuous-subarray/ 7 | * 8 | * Example 1: 9 | * Input: [1, 2, 5, 3, 7, 10, 9, 12] 10 | * Output: 5 11 | * Explanation: We need to sort only the subarray [5, 3, 7, 10, 9] to make the whole 12 | * array sorted 13 | * 14 | * Example 2: 15 | * Input: [1, 3, 2, 0, -1, 7, 10] 16 | * Output: 5 17 | * Explanation: We need to sort only the subarray [1, 3, 2, 0, -1] to make the whole 18 | * array sorted 19 | * 20 | * Time: O(n) 21 | * Space: O(1) 22 | * 23 | * @param {number[]} array 24 | * @return {number} 25 | */ 26 | function shortestUnsortedContinuousSubarray(array) { 27 | let left = 0; 28 | let right = array.length - 1; 29 | 30 | // Find 1st `left` to meet left > left + 1 (unsorted) 31 | while (left < array.length - 1 && array[left] <= array[left + 1]) { 32 | left++; 33 | } 34 | // fully sorted 35 | if (left === array.length - 1) { 36 | return []; 37 | } 38 | 39 | // Find 1st `right` to meet right < right - 1 (unsorted) 40 | while (right > 0 && array[right] >= array[right - 1]) { 41 | right--; 42 | } 43 | 44 | // fully reversely sorted 45 | if (left === 0 && right === array.length - 1) { 46 | return array.length; 47 | } 48 | 49 | /** 50 | * Those left/right are just candidates, we need to find the min/max between 51 | * left and right, and then: 52 | * * find the first `index1` in [0, left - 1] to meet index1 <= min 53 | * * find the first `index2` in [right + 1, array.length - 1] to meet index2 >= max 54 | * * `index1` and `index2` are the boundary for the subarray 55 | */ 56 | let min = Infinity; 57 | let max = -Infinity; 58 | for (let i = left; i <= right; i++) { 59 | min = Math.min(array[i], min); 60 | max = Math.max(array[i], max); 61 | } 62 | 63 | /** 64 | * Do not use binary search below, even the search range is guaranteed to be 65 | * sorted, but binary search can't handle the duplicate number. For example: 66 | * finding 2 in [2, 2, 2, 3] will return index 0, but what we need is index 2. 67 | * If we want to use binary search in this scenario, consider lower_bound() and 68 | * upper_bound(), that is: use upper_bound() for finding `left`, use lower_bound() 69 | * for finding `right`. 70 | */ 71 | 72 | // find min in [0, left - 1] 73 | while (left > 0 && array[left - 1] > min) { 74 | left--; 75 | } 76 | 77 | // find max in [right + 1, array.length - 1] 78 | while (right < array.length && array[right + 1] < max) { 79 | right++; 80 | } 81 | 82 | return right - left + 1; 83 | } 84 | 85 | // Test 86 | console.log(shortestUnsortedContinuousSubarray([1, 2, 5, 3, 7, 10, 9, 12])); // 5 87 | console.log(shortestUnsortedContinuousSubarray([1, 3, 2, 0, -1, 7, 10])); // 5 88 | -------------------------------------------------------------------------------- /10_subsets/320_generalized-abbreviation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given a word, write a function to generate all of its unique generalized 5 | * abbreviations. Generalized abbreviation of a word can be generated by replacing 6 | * each substring of the word by the count of characters in the substring. Take the 7 | * example of "ab" which has four substrings: "", "a", "b", and "ab". After replacing 8 | * these substrings in the actual word by the count of characters we get all the 9 | * generalized abbreviations: "2", "a1", "1b", and "ab". 10 | * https://leetcode.com/problems/generalized-abbreviation/ (subscription) 11 | * 12 | * Example 1: 13 | * Input: "BAT" 14 | * Output: "BAT", "BA1", "B1T", "B2", "1AT", "1A1", "2T", "3" 15 | * 16 | * Example 2: 17 | * Input: "code" 18 | * Output: "code", "cod1", "co1e", "co2", "c1de", "c1d1", "c2e", "c3", "1ode", 19 | * "1od1", "1o1e", "1o2", "2de", "2d1", "3e", "4" 20 | * 21 | * 22 | * Time: O(n 2^n) 23 | * Space: O(n 2^n) <- result 24 | * 25 | * @param {string} str 26 | * @return {string[]} 27 | */ 28 | function generateGeneralizedAbbreviation(str) { 29 | const abbreviations = []; 30 | _backtrack(str, 0, [], abbreviations); 31 | return abbreviations; 32 | } 33 | 34 | function _backtrack(str, curIndex, curList, result) { 35 | if (curIndex === str.length) { 36 | // O(n) to join array to string 37 | result.push(curList.join('')); 38 | return; 39 | } 40 | let choices = [str[curIndex]]; 41 | // if last char is a number, we can't choose number for next choice 42 | if (!(curList.length > 0 && _isNumber(curList[curList.length - 1]))) { 43 | const numberChoices = new Array(str.length - curIndex) 44 | .fill(0) 45 | .map((_, index) => index + 1); 46 | choices = choices.concat(numberChoices); 47 | } 48 | /** 49 | * How to calculate the time complexity? Think in this way: for each character 50 | * in the string, we can choose either to take it or use a number to represent 51 | * it, so basically for each position, we have 2 choices, so roughly it's O(2^n). 52 | */ 53 | for (let i = 0; i < choices.length; i++) { 54 | curList.push(choices[i]); 55 | if (i === 0) { 56 | // char 57 | _backtrack(str, curIndex + 1, curList, result); 58 | } else { 59 | // number 60 | _backtrack(str, curIndex + choices[i], curList, result); 61 | } 62 | curList.pop(); 63 | } 64 | } 65 | 66 | function _isNumber(char) { 67 | /** 68 | * `char > '0' && char < '9'` won't work because if `word.length >= 10`, then 69 | * `char` could be a double digit string. 70 | */ 71 | return !Number.isNaN(parseInt(char)); 72 | } 73 | 74 | // Test 75 | console.log(generateGeneralizedAbbreviation('BAT')); 76 | // [ 'BAT', 'BA1', 'B1T', 'B2', '1AT', '1A1', '2T', '3' ] 77 | console.log(generateGeneralizedAbbreviation('code')); 78 | // [ 'code', 'cod1', 'co1e', 'co2', 'c1de', 'c1d1', 'c2e', 'c3', '1ode', '1od1', '1o1e', '1o2', '2de', '2d1', '3e', '4' ] 79 | -------------------------------------------------------------------------------- /11_binary-search/33_search-in-rotated-sorted-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Problem: 4 | * Given an array of numbers which is sorted in ascending order and also rotated by some 5 | * arbitrary number, find if a given 'key' is present in it. Write a function to return 6 | * the index of the 'key' in the rotated array. If the 'key' is not present, return -1. 7 | * You can assume that the given array does not have any duplicates. 8 | * https://leetcode.com/problems/search-in-rotated-sorted-array/ 9 | * 10 | * Example 1: 11 | * Input: [10, 15, 1, 3, 8], key = 15 12 | * Output: 1 13 | * Explanation: '15' is present in the array at index '1'. 14 | * 15 | * Example 2: 16 | * Input: [4, 5, 7, 9, 10, -1, 2], key = 10 17 | * Output: 4 18 | * Explanation: '10' is present in the array at index '4'. 19 | * 20 | * 21 | * Time: O(log n) 22 | * Space: O(1) 23 | * 24 | * @param {number[]} nums 25 | * @param {number} key 26 | * @return {number} 27 | */ 28 | function searchRotatedArray(nums, key) { 29 | let start = 0; 30 | /** 31 | * Why not `end = nums.length` and `start < end`? Because in the loop we need to 32 | * use the value `nums[end]`, we need to make sure `end` is always a valid index, 33 | * otherwise additional check is required. 34 | */ 35 | let end = nums.length - 1; 36 | while (start <= end) { 37 | const middle = start + Math.floor((end - start) / 2); 38 | if (nums[middle] === key) { 39 | return middle; 40 | } 41 | /** 42 | * The trick part is here, how to always split the search range into 2 sections 43 | * with the rotated array? The core logic here is to compare the `middle` value 44 | * with the `start` value: 45 | * * if `middle` value is bigger, then at least [start, middle] part is sorted, 46 | * so we can quickly check if `key` falls in this section, if so, change `end`, 47 | * otherwise change `start` because key is in the other section. 48 | * * otherwise if `middle` value is smaller, then obviously [middle, end] is 49 | * sorted, so similarly we check if `key` falls in the sorted section and change 50 | * `start/end` accordingly. 51 | */ 52 | if (nums[middle] >= nums[start]) { 53 | // [start, middle] is sorted 54 | if (key >= nums[start] && key < nums[middle]) { 55 | end = middle - 1; 56 | } else { 57 | start = middle + 1; 58 | } 59 | } else { 60 | // [middle, end] is sorted 61 | if (key > nums[middle] && key <= nums[end]) { 62 | start = middle + 1; 63 | } else { 64 | end = middle - 1; 65 | } 66 | } 67 | } 68 | return -1; 69 | } 70 | 71 | // Test 72 | console.log(searchRotatedArray([10, 15, 1, 3, 8], 15)); // 1 73 | console.log(searchRotatedArray([10, 15, 1, 3, 8], 8)); // 4 74 | console.log(searchRotatedArray([10, 15, 1, 3, 8], 17)); // -1 75 | console.log(searchRotatedArray([10, 15, 1, 3, 8], 5)); // -1 76 | console.log(searchRotatedArray([4, 5, 7, 9, 10, -1, 2], 10)); // 4 77 | -------------------------------------------------------------------------------- /8_DFS/437_path-sum-iii.js: -------------------------------------------------------------------------------- 1 | const { buildTreeBFS } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given a binary tree and a number S, find all paths in the tree such that the sum 7 | * of all the node values of each path equals S. Please note that the paths can 8 | * start or end at any node but all paths must follow direction from parent to child 9 | * (top to bottom). 10 | * https://leetcode.com/problems/path-sum-iii/ 11 | * 12 | * Example 1: 13 | * Input: [1, 7, 9, 6, 5, 2, 3], 12 14 | * Output: 3 15 | * Explanation: There are three paths with sum 12: 7 -> 5, 1 -> 9 -> 2, and 9 -> 3 16 | * 17 | * Example 2: 18 | * Input: [12, 7, 1, null, 4, 10, 5], 11 19 | * Output: 2 20 | * Explanation: Here are the two paths with sum 11: 7 -> 4 . and 1 -> 10. 21 | * 22 | * 23 | * Time: O(nh) <- for each node it might need to traverse up until root 24 | * Space: O(h) `curPath` need to store the nodes for the maximum height 25 | * 26 | * @param {TreeNode} root 27 | * @param {number} sum 28 | * @return {number} 29 | */ 30 | function countPathOfSum(root, sum) { 31 | return _traverse(root, sum, []); 32 | } 33 | 34 | function _traverse(node, sum, curPath) { 35 | if (!node) { 36 | return 0; 37 | } 38 | curPath.push(node.val); 39 | 40 | /** 41 | * Why we don't modify the `sum` every time and pass it down to the children 42 | * nodes? Technically we can do that, but since the path doesn't need to start 43 | * with the root, so we can't actually reuse the modified sum for each node. 44 | * So here the logic is, for each visited node, calculate all the sum from 45 | * itself to all the upwards nodes until we reach root, during this process, 46 | * if we find a matched sum, plus 1 for the `count`. 47 | * 48 | * Is there any duplicate calculation? No, because for each node, it just calculates 49 | * the sum starting from the node itself (always including the node itself) to 50 | * the upwards nodes. This also defines the time complexity: O(n * h), for each 51 | * node, we need to traverse upwards until the root, which is the height of the 52 | * tree. 53 | */ 54 | let count = 0; 55 | let pathSum = 0; 56 | for (let i = curPath.length - 1; i >= 0; i--) { 57 | pathSum += curPath[i]; 58 | if (pathSum === sum) { 59 | count++; 60 | // can not break here cause there might be node with 0 value 61 | } 62 | } 63 | 64 | /** 65 | * Use the recursion return value to aggregate the count of its children 66 | * to the parent node, so the final return value for `root` will cover valid 67 | * count for all its children. 68 | */ 69 | count += _traverse(node.left, sum, curPath); 70 | count += _traverse(node.right, sum, curPath); 71 | curPath.pop(); 72 | return count; 73 | } 74 | 75 | // Test 76 | const tree1 = buildTreeBFS([1, 7, 9, 6, 5, 2, 3]); 77 | console.log(countPathOfSum(tree1, 12)); // 3 78 | 79 | const tree2 = buildTreeBFS([12, 7, 1, null, 4, 10, 5]); 80 | console.log(countPathOfSum(tree2, 11)); // 2 81 | -------------------------------------------------------------------------------- /14_k-way-merge/23_merge-k-sorted-lists.js: -------------------------------------------------------------------------------- 1 | const { Heap, buildLinkedList, printLinkedList } = require('../_utils'); 2 | 3 | /** 4 | * 5 | * Problem: 6 | * Given an array of K sorted LinkedLists, merge them into one sorted list. 7 | * https://leetcode.com/problems/merge-k-sorted-lists/ 8 | * 9 | * Example 1: 10 | * Input: L1=[2, 6, 8], L2=[3, 6, 7], L3=[1, 3, 4] 11 | * Output: [1, 2, 3, 3, 4, 6, 6, 7, 8] 12 | * 13 | * Example 2: 14 | * Input: L1=[5, 8, 9], L2=[1, 7] 15 | * Output: [1, 5, 7, 8, 9] 16 | * 17 | * 18 | * Note: the naive solution is to keep merging 2 lists until all the lists are 19 | * merged. Let's assume we have k lists, each list have n/k nodes (n nodes in total). 20 | * Merging 2 lists cost 2n/k time complexity, then we need to merge this 2n/k nodes 21 | * list with the next n/k nodes lists, so the cost will be 3n/k. Keep going and the 22 | * last merge will be (k-1)n/k nodes list with the n/k nodes list, so it will cost 23 | * n. The total time complexity will be: (2 + ... + k) * n/k = O(k^2 * n/k) = O(nk), 24 | * while our heap solution only costs O(n log(k)). 25 | * 26 | * Time: O(n log(k)) 27 | * Space: k <- for heap 28 | * 29 | * @param {ListNode[]} lists 30 | * @return {ListNode} 31 | */ 32 | function mergeKSortedLists(lists) { 33 | const minHeap = new Heap((a, b) => b.val - a.val); 34 | /** 35 | * Step 1: put the first node in each list into the heap, so the heap peak is 36 | * guaranteed to be the smallest node in all k lists. 37 | * Time: O(k log(k)) 38 | */ 39 | for (let i = 0; i < lists.length; i++) { 40 | if (lists[i]) { 41 | minHeap.push(lists[i]); 42 | } 43 | } 44 | // corner case, e.g. `lists` itself is empty, or all the sub lists are empty 45 | if (minHeap.size() === 0) { 46 | return []; 47 | } 48 | const newHeadNode = minHeap.peek(); 49 | let preNode = null; 50 | /** 51 | * Step 2: keep popping nodes from the heap, every time we pop one node out, we push 52 | * the next node of it into the heap. When the node popped out has no next, there 53 | * won't be new nodes being pushed into the heap, so the heap size will decrease, doing 54 | * this until the heap is empty. 55 | * Time: O(n log(k)) -> each node being pushed once and popped once 56 | */ 57 | while (minHeap.size() > 0) { 58 | const node = minHeap.pop(); 59 | if (preNode) { 60 | preNode.next = node; 61 | } 62 | if (node.next) { 63 | minHeap.push(node.next); 64 | } 65 | preNode = node; 66 | } 67 | return newHeadNode; 68 | } 69 | 70 | // Test 71 | const list1 = buildLinkedList([2, 6, 8]); 72 | const list2 = buildLinkedList([3, 6, 7]); 73 | const list3 = buildLinkedList([1, 3, 4]); 74 | const mergedList1 = mergeKSortedLists([list1, list2, list3]); 75 | printLinkedList(mergedList1); // 1 -> 2 -> 3 -> 3 -> 4 -> 6 -> 6 -> 7 -> 8 76 | 77 | const list4 = buildLinkedList([5, 8, 9]); 78 | const list5 = buildLinkedList([1, 7]); 79 | const mergedList2 = mergeKSortedLists([list4, list5]); 80 | printLinkedList(mergedList2); // 1 -> 5 -> 7 -> -> 9 81 | --------------------------------------------------------------------------------