diff --git a/README.md b/README.md index 1aafa74..7f7be89 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,20 @@ The listing below is sorted based on LeetCode #. If you are interested to see my | 49 | [Group Anagrams](/problems/group-anagrams) | Medium | Hash Table, String | | 53 | [Maximum Subarray](/problems/maximum-subarray) | Easy | Array, Divide and Conquer, Dynamic Programming | | 55 | [Jump Game](/problems/jump-game) | Medium | Array, Greedy | +| 60 | [Permutation Sequence](/problems/permutation-sequence) | Medium | Math, Backtracking | +| 62 | [Unique Paths](/problems/unique-paths) | Medium | Array, Dynamic Programming | | 64 | [Minimum Path Sum](/problems/minimum-path-sum) | Medium | Array, Dynamic Programming | | 70 | [Climbing Stairs](/problems/climbing-stairs) | Easy | Dynamic Programming | | 72 | [Edit Distance](/problems/edit-distance) | Hard | String, Dynamic Programming | +| 75 | [Sort Colors](/problems/sort-colors) | Medium | Array, Two Pointers, Sort | +| 96 | [Unique Binary Search Trees](/problems/unique-binary-search-trees) | Medium | Dynamic Programming, Tree | | 121 | [Best Time to Buy and Sell Stock](/problems/best-time-to-buy-and-sell-stock) | Easy | Array, Dynamic Programming | | 122 | [Best Time to Buy and Sell Stock II](/problems/best-time-to-buy-and-sell-stock-ii) | Easy | Array, Greedy | | 124 | [Binary Tree Maximum Path Sum](/problems/binary-tree-maximum-path-sum) | Hard | Tree, Depth-first Search | +| 129 | [Sum Root to Leaf Numbers](/problems/sum-root-to-leaf-numbers) | Medium | Tree, Depth-first Search | +| 130 | [Surrounded Regions](/problems/surrounded-regions) | Medium | Depth-first Search, Breadth-first Search, Union Find | | 136 | [Single Number](/problems/single-number) | Easy | Hash table, bit manipulation | +| 137 | [Single Number II](/problems/single-number-ii) | Medium | Bit Manipulation | | 146 | [LRU Cache](/problems/lru-cache) | Medium | Design | | 155 | [Min Stack](/problems/min-stack) | Easy | Stack, Design | | 169 | [Majority Element](/problems/majority-element) | Easy | Array, Divide and Conquer, Bit Manipulation | @@ -70,33 +77,52 @@ The listing below is sorted based on LeetCode #. If you are interested to see my | 202 | [Happy Number](/problems/happy-number) | Easy | Hash Table, Math | | 207 | [Course Schedule](/problems/course-schedule) | Medium | Depth-first Search, Breadth-first Search, Graph, Topological Sort | | 208 | [Implement Trie (Prefix Tree)](/problems/implement-trie-prefix-tree) | Medium | Design, Trie | +| 212 | [Word Search II](/problems/word-search-ii) | Hard | Backtracking, Trie | | 221 | [Maximal Square](/problems/maximal-square) | Medium | Dynamic Programming | +| 222 | [Count Complete Tree Nodes](/problems/count-complete-tree-nodes) | Medium | Binary Search, Tree | +| 226 | [Invert Binary Tree](/problems/invert-binary-tree) | Easy | Tree | | 230 | [Kth Smallest Element in a BST](/problems/kth-smallest-element-in-a-bst) | Medium | Binary Search, Tree | +| 231 | [Power of Two](/problems/power-of-two) | Easy | Math, Bit Manipulation | +| 237 | [Delete Node in a Linked List](/problems/delete-node-in-a-linked-list) | Easy | Linked List | | 238 | [Product of Array Except Self](/problems/product-of-array-except-self) | Medium | Array | +| 275 | [H-Index II](/problems/h-index-ii) | Medium | Binary Search | | 278 | [First Bad Version](/problems/first-bad-version) | Easy | Binary Search | +| 279 | [Perfect Squares](/problems/perfect-squares) | Medium | Math, Dynamic Programming, Breadth-first Search | | 283 | [Move Zeroes](/problems/move-zeroes) | Easy | Array, Two Pointers | +| 287 | [Find the Duplicate Number](/problems/find-the-duplicate-number) | Medium | Array, Two Pointers, Binary Search | | 303 | [Range Sum Query - Immutable](/problems/range-sum-query-immutable) | Easy | Dynamic Programming | | 328 | [Odd Even Linked List](/problems/odd-even-linked-list) | Medium | Linked List | +| 332 | [Reconstruct Itinerary](/problems/reconstruct-itinerary) | Medium | Depth-first Search, Graph | | 338 | [Counting Bits](/problems/counting-bits) | Medium | Dynamic Programming, Bit Manipulation | +| 344 | [Reverse String](/problems/reverse-string) | Easy | Two Pointers, String | | 349 | [Intersection of Two Arrays](/problems/intersection-of-two-arrays) | Easy | Hash table, two pointers, binary search, sort, set | | 350 | [Intersection of Two Arrays II](/problems/intersection-of-two-arrays-ii) | Easy | Hash Table, two pointers, binary search, sort | | 367 | [Valid Perfect Square](/problems/valid-perfect-square) | Easy | Math, Binary Search | +| 368 | [Largest Divisible Subset](/problems/largest-divisible-subset) | Medium | Math, Dynamic Programming | +| 380 | [Insert Delete GetRandom O(1)](/problems/insert-delete-getrandom-o1) | Medium | Array, Hash Table, Design | | 383 | [Ransom Note](/problems/ransom-note) | Easy | String | | 387 | [First Unique Character in a String](/problems/first-unique-character-in-a-string) | Easy | Hash Table, String | | 392 | [Is Subsequence](/problems/is-subsequence) | Easy | Binary Search, Dynamic Programming, Greedy | | 402 | [Remove K Digits](/problems/remove-k-digits) | Medium | Stack, Greedy | +| 406 | [Queue Reconstruction by Height](/problems/queue-reconstruction-by-height) | Medium | Greedy | | 438 | [Find All Anagrams in a String](/problems/find-all-anagrams-in-a-string) | Medium | Hash Table | | 451 | [Sort Characters By Frequency](/problems/sort-characters-by-frequency) | Medium | Hash Table, Heap | +| 468 | [Validate IP Address](/problems/validate-ip-address) | Medium | String | | 476 | [Number Complement](/problems/number-complement) | Easy | Bit Manipulation | +| 518 | [Coin Change 2](/problems/coin-change-2) | Medium | - | | 525 | [Contiguous Array](/problems/contiguous-array) | Medium | Hash Table | +| 528 | [Random Pick with Weight](/problems/random-pick-with-weight) | Medium | Binary Search, Random | | 540 | [Single Element in a Sorted Array](/problems/single-element-in-a-sorted-array) | Medium | - | | 543 | [Diameter of Binary Tree](/problems/diameter-of-binary-tree) | Easy | Tree | | 560 | [Subarray Sum Equals K](/problems/subarray-sum-equals-k) | Medium | Array, Hash Table | | 567 | [Permutation in String](/problems/permutation-in-string) | Medium | Two Pointers, Sliding Window | | 678 | [Valid Parenthesis String](/problems/valid-parenthesis-string) | Medium | String | +| 700 | [Search in a Binary Search Tree](/problems/search-in-a-binary-search-tree) | Easy | Tree | +| 714 | [Dungeon Game](/problems/dungeon-game) | Hard | Binary Search, Dynamic Programming | | 733 | [Flood Fill](/problems/flood-fill) | Easy | Depth-first Search | | 746 | [Min Cost Climbing Stairs](/problems/min-cost-climbing-stairs) | Easy | Array, Dynamic Programming | | 771 | [Jewels and Stones](/problems/jewels-and-stones) | Easy | Hash Table | +| 787 | [Cheapest Flights Within K Stops](/problems/cheapest-flights-within-k-stops) | Medium | Dynamic Programming, Heap, Breadth-first Search | | 844 | [Backspace String Compare](/problems/backspace-string-compare) | Easy | Two Pointers, Stack | | 876 | [Middle of the Linked List](/problems/middle-of-the-linked-list) | Easy | Linked List | | 886 | [Possible Bipartition](/problems/possible-bipartition) | Medium | Depth-first Search | @@ -108,7 +134,9 @@ The listing below is sorted based on LeetCode #. If you are interested to see my | 997 | [Find the Town Judge](/problems/find-the-town-judge) | Easy | Graph | | 1008 | [Construct Binary Search Tree from Preorder Traversal](/problems/construct-binary-search-tree-from-preorder-traversal) | Medium | Tree | | 1025 | [Divisor Game](/problems/divisor-game) | Easy | Math, Dynamic Programming | +| 1029 | [Two City Scheduling](/problems/two-city-scheduling) | Easy | Greedy | | 1035 | [Uncrossed Lines](/problems/uncrossed-lines) | Medium | Array | +| 1044 | [Longest Duplicate Substring](/problems/longest-duplicate-substring) | Hard | Hash Table, Binary Search | | 1046 | [Last Stone Weight](/problems/last-stone-weight) | Easy | Heap, Greedy | | 1143 | [Longest Common Subsequence](/problems/longest-common-subsequence) | Medium | Dynamic Programming | | 1144 | [Decrease Elements To Make Array Zigzag](/problems/decrease-elements-to-make-array-zigzag) | Medium | Array | @@ -180,7 +208,7 @@ The listing below is sorted based on LeetCode #. If you are interested to see my | 1476 | [Subrectangle Queries](/problems/subrectangle-queries) | Medium | Array | | 1480 | [Running Sum of 1d Array](/problems/running-sum-of-1d-array) | Easy | Array | | 1481 | [Least Number of Unique Integers after K Removals](/problems/least-number-of-unique-integers-after-k-removals) | Medium | Array, Sort | -| 1491 | [Average Salary Excluding the Minimum and Maximum Salary](/problems/average-salary-excluding-the-minimum-and-maximum-salary) | Easy | Array, Sort. | +| 1491 | [Average Salary Excluding the Minimum and Maximum Salary](/problems/average-salary-excluding-the-minimum-and-maximum-salary) | Easy | Array, Sort | | 1492 | [The kth Factor of n](/problems/the-kth-factor-of-n) | Medium | Math | | 1493 | [Longest Subarray of 1's After Deleting One Element](/problems/longest-subarray-of-1s-after-deleting-one-element) | Medium | Array | diff --git a/package-lock.json b/package-lock.json index cc7641f..0549bea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "leetcode", - "version": "1.27.0", + "version": "1.28.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 46c85a4..c8e5646 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leetcode", - "version": "1.27.0", + "version": "1.28.0", "description": "My solutions for LeetCode problems.", "main": "index.js", "scripts": { diff --git a/problems/cheapest-flights-within-k-stops/README.md b/problems/cheapest-flights-within-k-stops/README.md new file mode 100644 index 0000000..06454fd --- /dev/null +++ b/problems/cheapest-flights-within-k-stops/README.md @@ -0,0 +1,54 @@ +# Cheapest Flights Within K Stops + +LeetCode #: [787](https://leetcode.com/problems/cheapest-flights-within-k-stops/) + +Difficulty: Medium + +Topics: Dynamic Programming, Heap, Breadth-first Search. + +## Problem + +There are `n` cities connected by `m` flights. Each flight starts from city `u` and arrives at `v` with a price `w`. + +Now given all the cities and flights, together with starting city `src` and the destination `dst`, your task is to find the cheapest price from `src` to `dst` with up to `k` stops. If there is no such route, output `-1`. + +Example 1: + +![image](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/02/16/995.png) + +```text +Input: +n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]] +src = 0, dst = 2, k = 1 +Output: 200 +Explanation: +The cheapest price from city 0 to city 2 with at most 1 stop costs 200, as marked red in the picture. +``` + +Example 2: + +![image](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/02/16/995.png) + +```text +Input: +n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]] +src = 0, dst = 2, k = 0 +Output: 500 +Explanation: +The cheapest price from city 0 to city 2 with at most 0 stop costs 500, as marked blue in the picture. +``` + +Constraints: + +- The number of nodes `n` will be in range `[1, 100]`, with nodes labeled from `0` to `n - 1`. +- The size of flights will be in range `[0, n * (n - 1) / 2]`. +- The format of each flight will be `(src, dst, price)`. +- The price of each flight will be in the range `[1, 10000]`. +- `k` is in the range of `[0, n - 1]`. +- There will not be any duplicated flights or self cycles. + +## Solution Explanation + +The idea is based on [Dijkstra's algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm). + +Reference: [[Java/Python] Priority Queue Solution](https://leetcode.com/problems/cheapest-flights-within-k-stops/discuss/115541/JavaPython-Priority-Queue-Solution) by [lee215](https://leetcode.com/lee215) diff --git a/problems/cheapest-flights-within-k-stops/findCheapestPrice.js b/problems/cheapest-flights-within-k-stops/findCheapestPrice.js new file mode 100644 index 0000000..2c77ae7 --- /dev/null +++ b/problems/cheapest-flights-within-k-stops/findCheapestPrice.js @@ -0,0 +1,53 @@ +const sortedIndexBy = require('lodash/sortedIndexBy') + +// get a src->dst->price map object based on flights. +const getSrcDstPriceMap = (flights) => { + const srcDstPriceMap = new Map() + + for (let i = 0; i < flights.length; i++) { + const [src, dst, price] = flights[i] + srcDstPriceMap.set(src, (srcDstPriceMap.get(src) || new Map()).set(dst, price)) + } + + return srcDstPriceMap +} + +/** + * @param {number} n + * @param {number[][]} flights + * @param {number} src + * @param {number} dst + * @param {number} K + * @return {number} + */ +const findCheapestPrice = function (n, flights, src, dst, k) { + const srcDstPriceMap = getSrcDstPriceMap(flights) + + // a priority queue that sorts based on accumulated price. + // each item is in the format [accumulated price, current city, remaining flights]. + // note that remaining flights is equal to k+1. + const queue = [] + queue.push([0, src, k + 1]) + + while (queue.length > 0) { + const [accPrice, city, remainingFlights] = queue.shift() + + if (city === dst) { + return accPrice + } + + if (remainingFlights > 0) { + const adjDstPriceMap = srcDstPriceMap.get(city) || new Map() + + for (const [dst, price] of adjDstPriceMap.entries()) { + const next = [accPrice + price, dst, remainingFlights - 1] + const idx = sortedIndexBy(queue, next, (o) => o[0]) + queue.splice(idx, 0, next) + } + } + } + + return -1 +} + +module.exports = findCheapestPrice diff --git a/problems/cheapest-flights-within-k-stops/findCheapestPrice.test.js b/problems/cheapest-flights-within-k-stops/findCheapestPrice.test.js new file mode 100644 index 0000000..8a650dc --- /dev/null +++ b/problems/cheapest-flights-within-k-stops/findCheapestPrice.test.js @@ -0,0 +1,49 @@ +const findCheapestPrice = require('./findCheapestPrice') + +test('Example 1', () => { + const n = 3 + const flights = [[0, 1, 100], [1, 2, 100], [0, 2, 500]] + const src = 0 + const dst = 2 + const k = 1 + + const result = findCheapestPrice(n, flights, src, dst, k) + + expect(result).toBe(200) +}) + +test('Example 2', () => { + const n = 3 + const flights = [[0, 1, 100], [1, 2, 100], [0, 2, 500]] + const src = 0 + const dst = 2 + const k = 0 + + const result = findCheapestPrice(n, flights, src, dst, k) + + expect(result).toBe(500) +}) + +test('no such route should return -1', () => { + const n = 4 + const flights = [[0, 1, 100], [1, 2, 100], [0, 2, 500], [2, 3, 200]] + const src = 0 + const dst = 3 + const k = 0 + + const result = findCheapestPrice(n, flights, src, dst, k) + + expect(result).toBe(-1) +}) + +test('intermediate city 4 has no flight out', () => { + const n = 4 + const flights = [[0, 1, 100], [1, 4, 120], [0, 2, 500], [2, 3, 200]] + const src = 0 + const dst = 3 + const k = 5 + + const result = findCheapestPrice(n, flights, src, dst, k) + + expect(result).toBe(700) +}) diff --git a/problems/coin-change-2/README.md b/problems/coin-change-2/README.md new file mode 100644 index 0000000..5517cf3 --- /dev/null +++ b/problems/coin-change-2/README.md @@ -0,0 +1,61 @@ +# Coin Change 2 + +LeetCode #: [518](https://leetcode.com/problems/coin-change-2/) + +Difficulty: Medium + +Topics: - + +## Problem + +You are given coins of different denominations and a total amount of money. Write a function to compute the number of combinations that make up that amount. You may assume that you have infinite number of each kind of coin. + +Example 1: + +```text +Input: amount = 5, coins = [1, 2, 5] +Output: 4 +Explanation: there are four ways to make up the amount: +5=5 +5=2+2+1 +5=2+1+1+1 +5=1+1+1+1+1 +``` + +Example 2: + +```text +Input: amount = 3, coins = [2] +Output: 0 +Explanation: the amount of 3 cannot be made up just with coins of 2. +``` + +Example 3: + +```text +Input: amount = 10, coins = [10] +Output: 1 +``` + +Note: + +You can assume that + +- `0 <= amount <= 5000` +- `1 <= coin <= 5000` +- the number of coins is less than 500 +- the answer is guaranteed to fit into signed 32-bit integer + +## Solution Explanation + +This is a [knapsack problem](https://en.wikipedia.org/wiki/Knapsack_problem) which can be solved by using dynamic programming. + +Reference: [Knapsack problem - Java solution with thinking process O(nm) Time and O(m) Space](https://leetcode.com/problems/coin-change-2/discuss/99212/Knapsack-problem-Java-solution-with-thinking-process-O(nm)-Time-and-O(m)-Space) by [tankztc](https://leetcode.com/tankztc) + +## Complexity Analysis + +Assume m is the amount and n is the number of coins. + +Time complexity: O(mn) + +Space complexity: O(m) diff --git a/problems/coin-change-2/change.js b/problems/coin-change-2/change.js new file mode 100644 index 0000000..9cbeb2c --- /dev/null +++ b/problems/coin-change-2/change.js @@ -0,0 +1,18 @@ +/** + * @param {number} amount + * @param {number[]} coins + * @return {number} + */ +const change = function (amount, coins) { + const dp = new Array(amount + 1).fill(0) + dp[0] = 1 + for (const coin of coins) { + for (let i = coin; i <= amount; i++) { + dp[i] += dp[i - coin] + } + } + + return dp[amount] +} + +module.exports = change diff --git a/problems/coin-change-2/change.test.js b/problems/coin-change-2/change.test.js new file mode 100644 index 0000000..d50b7e3 --- /dev/null +++ b/problems/coin-change-2/change.test.js @@ -0,0 +1,37 @@ +const change = require('./change') + +test('Example 1', () => { + const amount = 5 + const coins = [1, 2, 5] + + const result = change(amount, coins) + + expect(result).toBe(4) +}) + +test('Example 2', () => { + const amount = 3 + const coins = [2] + + const result = change(amount, coins) + + expect(result).toBe(0) +}) + +test('Example 3', () => { + const amount = 10 + const coins = [10] + + const result = change(amount, coins) + + expect(result).toBe(1) +}) + +test('amount 0 should return 1 (empty combination)', () => { + const amount = 0 + const coins = [1, 2, 5] + + const result = change(amount, coins) + + expect(result).toBe(1) +}) diff --git a/problems/count-complete-tree-nodes/README.md b/problems/count-complete-tree-nodes/README.md new file mode 100644 index 0000000..336d503 --- /dev/null +++ b/problems/count-complete-tree-nodes/README.md @@ -0,0 +1,33 @@ +# Count Complete Tree Nodes + +LeetCode #: [222](https://leetcode.com/problems/count-complete-tree-nodes/) + +Difficulty: Medium + +Topic: Binary Search, Tree. + +## Problem + +Given a complete binary tree, count the number of nodes. + +Note: + +Definition of a complete binary tree from [Wikipedia](http://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees): +In a complete binary tree every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h. + +Example: + +```text +Input: + 1 + / \ + 2 3 + / \ / +4 5 6 + +Output: 6 +``` + +## Solution Explanation + +Reference: [Concise Java solutions O(log(n)^2)](https://leetcode.com/problems/count-complete-tree-nodes/discuss/61958/Concise-Java-solutions-O(log(n)2)) by [StefanPochmann](https://leetcode.com/stefanpochmann) diff --git a/problems/count-complete-tree-nodes/countNodes.js b/problems/count-complete-tree-nodes/countNodes.js new file mode 100644 index 0000000..238652b --- /dev/null +++ b/problems/count-complete-tree-nodes/countNodes.js @@ -0,0 +1,34 @@ +const getHeight = (node) => { + return (node === null) + ? -1 + : 1 + getHeight(node.left) +} + +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {number} + */ +const countNodes = function (root) { + const rootHeight = getHeight(root) + + if (rootHeight < 0) { + return 0 + } + + const rightNodeHeight = getHeight(root.right) + if (rightNodeHeight === rootHeight - 1) { + return (1 << rootHeight) + countNodes(root.right) + } + + return (1 << rootHeight - 1) + countNodes(root.left) +} + +module.exports = countNodes diff --git a/problems/count-complete-tree-nodes/countNodes.test.js b/problems/count-complete-tree-nodes/countNodes.test.js new file mode 100644 index 0000000..68e2540 --- /dev/null +++ b/problems/count-complete-tree-nodes/countNodes.test.js @@ -0,0 +1,10 @@ +const { deserialize } = require('leettree') +const countNodes = require('./countNodes') + +test('Example 1', () => { + const root = deserialize([1, 2, 3, 4, 5, 6]) + + const result = countNodes(root) + + expect(result).toBe(6) +}) diff --git a/problems/delete-node-in-a-linked-list/README.md b/problems/delete-node-in-a-linked-list/README.md new file mode 100644 index 0000000..5dabda6 --- /dev/null +++ b/problems/delete-node-in-a-linked-list/README.md @@ -0,0 +1,44 @@ +# Delete Node in a Linked List + +LeetCode #: [237](https://leetcode.com/problems/delete-node-in-a-linked-list/) + +Difficulty: Easy + +Topic: Linked List. + +## Problem + +Write a function to delete a node (except the tail) in a singly linked list, given only access to that node. + +Given linked list -- head = [4,5,1,9], which looks like following: + +![](https://assets.leetcode.com/uploads/2018/12/28/237_example.png) + +Example 1: + +```text +Input: head = [4,5,1,9], node = 5 +Output: [4,1,9] +Explanation: You are given the second node with value 5, the linked list should become 4 -> 1 -> 9 after calling your function. +``` + +Example 2: + +```text +Input: head = [4,5,1,9], node = 1 +Output: [4,5,9] +Explanation: You are given the third node with value 1, the linked list should become 4 -> 5 -> 9 after calling your function. +``` + +Note: + +- The linked list will have at least two elements. +- All of the nodes' values will be unique. +- The given node will not be the tail and it will always be a valid node of the linked list. +- Do not return anything from your function. + +## Complexity Analysis + +Time complexity: O(1) + +Space complexity: O(1) diff --git a/problems/delete-node-in-a-linked-list/deleteNode.js b/problems/delete-node-in-a-linked-list/deleteNode.js new file mode 100644 index 0000000..4c0da82 --- /dev/null +++ b/problems/delete-node-in-a-linked-list/deleteNode.js @@ -0,0 +1,17 @@ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} node + * @return {void} Do not return anything, modify node in-place instead. + */ +const deleteNode = function (node) { + node.val = node.next.val + node.next = node.next.next +} + +module.exports = deleteNode diff --git a/problems/delete-node-in-a-linked-list/deleteNode.test.js b/problems/delete-node-in-a-linked-list/deleteNode.test.js new file mode 100644 index 0000000..24c0ec6 --- /dev/null +++ b/problems/delete-node-in-a-linked-list/deleteNode.test.js @@ -0,0 +1,29 @@ +const { deserialize, serialize } = require('../../utils/singlyLinkedList') +const deleteNode = require('./deleteNode') + +const getNode = (list, num) => { + let node = list + while (node.val !== num) { + node = node.next + } + + return node +} + +test('Example 1', () => { + const head = deserialize([4, 5, 1, 9]) + const node = getNode(head, 5) + + deleteNode(node) + + expect(serialize(head)).toStrictEqual([4, 1, 9]) +}) + +test('Example 2', () => { + const head = deserialize([4, 5, 1, 9]) + const node = getNode(head, 1) + + deleteNode(node) + + expect(serialize(head)).toStrictEqual([4, 5, 9]) +}) diff --git a/problems/dungeon-game/README.md b/problems/dungeon-game/README.md new file mode 100644 index 0000000..3a50fa7 --- /dev/null +++ b/problems/dungeon-game/README.md @@ -0,0 +1,34 @@ +# Dungeon Game + +LeetCode #: [174](https://leetcode.com/problems/dungeon-game/) + +Difficulty: Hard + +Topic: Binary Search, Dynamic Programming. + +## Problem + +The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. The dungeon consists of M x N rooms laid out in a 2D grid. Our valiant knight (K) was initially positioned in the top-left room and must fight his way through the dungeon to rescue the princess. + +The knight has an initial health point represented by a positive integer. If at any point his health point drops to 0 or below, he dies immediately. + +Some of the rooms are guarded by demons, so the knight loses health (negative integers) upon entering these rooms; other rooms are either empty (0's) or contain magic orbs that increase the knight's health (positive integers). + +In order to reach the princess as quickly as possible, the knight decides to move only rightward or downward in each step. + +**Write a function to determine the knight's minimum initial health so that he is able to rescue the princess.** + +For example, given the dungeon below, the initial health of the knight must be at least 7 if he follows the optimal path `RIGHT -> RIGHT -> DOWN -> DOWN`. + +```text +(K) -2 | -3 | 3 +-------+-----+------- + -5 | -10 | 1 +-------+-----+------- + 10 | 30 | -5 (P) +``` + +Note: + +- The knight's health has no upper bound. +- Any room can contain threats or power-ups, even the first room the knight enters and the bottom-right room where the princess is imprisoned. diff --git a/problems/dungeon-game/calculateMinimumHP.js b/problems/dungeon-game/calculateMinimumHP.js new file mode 100644 index 0000000..8cfef2b --- /dev/null +++ b/problems/dungeon-game/calculateMinimumHP.js @@ -0,0 +1,32 @@ +const minHealth = 1 + +/** + * @param {number[][]} dungeon + * @return {number} + */ +const calculateMinimumHP = function (dungeon) { + const rows = dungeon.length + const cols = dungeon[0].length + + for (let i = rows - 1; i >= 0; i--) { + for (let j = cols - 1; j >= 0; j--) { + const healthChange = dungeon[i][j] + + // special case: starting bottom right corner value, + // the final health value must be at least 1. + if (i === rows - 1 && j === cols - 1) { + dungeon[i][j] = Math.max(minHealth, minHealth - healthChange) + continue + } + + const rightHealth = dungeon[i][j + 1] || Number.MAX_SAFE_INTEGER + const downHealth = (dungeon[i + 1] && dungeon[i + 1][j]) || Number.MAX_SAFE_INTEGER + + dungeon[i][j] = Math.max(minHealth, Math.min(rightHealth, downHealth) - healthChange) + } + } + + return dungeon[0][0] +} + +module.exports = calculateMinimumHP diff --git a/problems/dungeon-game/calculateMinimumHP.test.js b/problems/dungeon-game/calculateMinimumHP.test.js new file mode 100644 index 0000000..b4a5859 --- /dev/null +++ b/problems/dungeon-game/calculateMinimumHP.test.js @@ -0,0 +1,13 @@ +const calculateMinimumHP = require('./calculateMinimumHP') + +test('Example 1', () => { + const dungeon = [ + [-2, -3, 3], + [-5, -10, 1], + [10, 30, -5] + ] + + const result = calculateMinimumHP(dungeon) + + expect(result).toBe(7) +}) diff --git a/problems/find-the-duplicate-number/README.md b/problems/find-the-duplicate-number/README.md new file mode 100644 index 0000000..9c55a39 --- /dev/null +++ b/problems/find-the-duplicate-number/README.md @@ -0,0 +1,42 @@ +# Find the Duplicate Number + +LeetCode #: [287](https://leetcode.com/problems/find-the-duplicate-number/) + +Difficulty: Medium + +Topics: Array, Two Pointers, Binary Search. + +## Problem + +Given an array `nums` containing `n` + 1 integers where each integer is between 1 and `n` (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one. + +Example 1: + +```text +Input: [1,3,4,2,2] +Output: 2 +``` + +Example 2: + +```text +Input: [3,1,3,4,2] +Output: 3 +``` + +Note: + +- You must not modify the array (assume the array is read only). +- You must use only constant, O(1) extra space. +- Your runtime complexity should be less than O(n^2). +- There is only one duplicate number in the array, but it could be repeated more than once. + +## Solution Explanation + +Reference: + +- [My easy understood solution with O(n) time and O(1) space without modifying the array. With clear explanation.](https://leetcode.com/problems/find-the-duplicate-number/discuss/72846/My-easy-understood-solution-with-O(n)-time-and-O(1)-space-without-modifying-the-array.-With-clear-explanation.) by [echoxiaolee](https://leetcode.com/echoxiaolee) + +- [https://keithschwarz.com/interesting/code/?dir=find-duplicate](https://keithschwarz.com/interesting/code/?dir=find-duplicate) + +- [Cycle Detection](https://en.wikipedia.org/wiki/Cycle_detection) on Wikipedia (algorithm by [Robert W. Floyd](https://en.wikipedia.org/wiki/Robert_W._Floyd) / [Donald Knuth](https://en.wikipedia.org/wiki/Donald_Knuth)) diff --git a/problems/find-the-duplicate-number/findDuplicate.js b/problems/find-the-duplicate-number/findDuplicate.js new file mode 100644 index 0000000..48c2f13 --- /dev/null +++ b/problems/find-the-duplicate-number/findDuplicate.js @@ -0,0 +1,23 @@ +/** + * @param {number[]} nums + * @return {number} + */ +const findDuplicate = function (nums) { + let slow = nums[0] + let fast = nums[nums[0]] + + while (slow !== fast) { + slow = nums[slow] + fast = nums[nums[fast]] + } + + fast = 0 + while (fast !== slow) { + fast = nums[fast] + slow = nums[slow] + } + + return slow +} + +module.exports = findDuplicate diff --git a/problems/find-the-duplicate-number/findDuplicate.test.js b/problems/find-the-duplicate-number/findDuplicate.test.js new file mode 100644 index 0000000..1d78daa --- /dev/null +++ b/problems/find-the-duplicate-number/findDuplicate.test.js @@ -0,0 +1,17 @@ +const findDuplicate = require('./findDuplicate') + +test('Example 1', () => { + const nums = [1, 3, 4, 2, 2] + + const result = findDuplicate(nums) + + expect(result).toBe(2) +}) + +test('Example 2', () => { + const nums = [3, 1, 3, 4, 2] + + const result = findDuplicate(nums) + + expect(result).toBe(3) +}) diff --git a/problems/h-index-ii/README.md b/problems/h-index-ii/README.md new file mode 100644 index 0000000..c83e393 --- /dev/null +++ b/problems/h-index-ii/README.md @@ -0,0 +1,32 @@ +# H-Index II + +LeetCode #: [275](https://leetcode.com/problems/h-index-ii/) + +Difficulty: Medium. + +Topics: Binary Search. + +## Problems + +Given an array of citations sorted in ascending order (each citation is a non-negative integer) of a researcher, write a function to compute the researcher's h-index. + +According to the [definition of h-index on Wikipedia](https://en.wikipedia.org/wiki/H-index): "A scientist has index h if h of his/her N papers have at least h citations each, and the other N − h papers have no more than h citations each." + +Example: + +```text +Input: citations = [0,1,3,5,6] +Output: 3 +Explanation: +[0,1,3,5,6] means the researcher has 5 papers in total and each of them had received 0, 1, 3, 5, 6 citations respectively. +Since the researcher has 3 papers with at least 3 citations each and the remaining two with no more than 3 citations each, her h-index is 3. +``` + +Note: + +If there are several possible values for h, the maximum one is taken as the h-index. + +Follow up: + +- This is a follow up problem to [H-Index](https://leetcode.com/problems/h-index/), where citations is now guaranteed to be sorted in ascending order. +- Could you solve it in logarithmic time complexity? diff --git a/problems/h-index-ii/hIndex.js b/problems/h-index-ii/hIndex.js new file mode 100644 index 0000000..0b517a6 --- /dev/null +++ b/problems/h-index-ii/hIndex.js @@ -0,0 +1,27 @@ +/** + * @param {number[]} citations + * @return {number} + */ +const hIndex = function (citations) { + const N = citations.length + let h = 0 + let lo = 0 + let hi = citations.length - 1 + + while (lo <= hi) { + const mid = lo + Math.floor((hi - lo) / 2) + const num = citations[mid] + const paperCount = N - mid + + if (num >= paperCount) { + h = Math.max(paperCount, h) + hi = mid - 1 + } else { + lo = mid + 1 + } + } + + return h +} + +module.exports = hIndex diff --git a/problems/h-index-ii/hIndex.test.js b/problems/h-index-ii/hIndex.test.js new file mode 100644 index 0000000..ac58e46 --- /dev/null +++ b/problems/h-index-ii/hIndex.test.js @@ -0,0 +1,49 @@ +const hIndex = require('./hIndex') + +test('Example 1', () => { + const citations = [0, 1, 3, 5, 6] + + const result = hIndex(citations) + + expect(result).toBe(3) +}) + +test('4/5 papers with a least 4 citations', () => { + const citations = [0, 4, 5, 6, 7] + + const result = hIndex(citations) + + expect(result).toBe(4) +}) + +test('1 paper with 1 citation', () => { + const citations = [0, 0, 1] + + const result = hIndex(citations) + + expect(result).toBe(1) +}) + +test('3 papers with 1 citation', () => { + const citations = [1, 1, 1] + + const result = hIndex(citations) + + expect(result).toBe(1) +}) + +test('1 paper with 0 citation', () => { + const citations = [0] + + const result = hIndex(citations) + + expect(result).toBe(0) +}) + +test('1 paper with 100 citation', () => { + const citations = [100] + + const result = hIndex(citations) + + expect(result).toBe(1) +}) diff --git a/problems/insert-delete-getrandom-o1/README.md b/problems/insert-delete-getrandom-o1/README.md new file mode 100644 index 0000000..7072be7 --- /dev/null +++ b/problems/insert-delete-getrandom-o1/README.md @@ -0,0 +1,49 @@ +# Insert Delete GetRandom O(1) + +LeetCode #: [380](https://leetcode.com/problems/insert-delete-getrandom-o1/) + +Difficulty: Medium + +Topic: Array, Hash Table, Design. + +## Problem + +Design a data structure that supports all following operations in average O(1) time. + +1. `insert(val)`: Inserts an item val to the set if not already present. +2. `remove(val)`: Removes an item val from the set if present. +3. `getRandom`: Returns a random element from current set of elements. Each element must have the same probability of being returned. + +Example: + +```text +// Init an empty set. +RandomizedSet randomSet = new RandomizedSet(); + +// Inserts 1 to the set. Returns true as 1 was inserted successfully. +randomSet.insert(1); + +// Returns false as 2 does not exist in the set. +randomSet.remove(2); + +// Inserts 2 to the set, returns true. Set now contains [1,2]. +randomSet.insert(2); + +// getRandom should return either 1 or 2 randomly. +randomSet.getRandom(); + +// Removes 1 from the set, returns true. Set now contains [2]. +randomSet.remove(1); + +// 2 was already in the set, so return false. +randomSet.insert(2); + +// Since 2 is the only number in the set, getRandom always return 2. +randomSet.getRandom(); +``` + +## Complexity Analysis + +Time complexity: O(1) for all `insert`, `remove` and `getRandom` operations. + +Space complexity: O(n) where n is the number of elements in the set. diff --git a/problems/insert-delete-getrandom-o1/RandomizedSet.js b/problems/insert-delete-getrandom-o1/RandomizedSet.js new file mode 100644 index 0000000..4ea838a --- /dev/null +++ b/problems/insert-delete-getrandom-o1/RandomizedSet.js @@ -0,0 +1,67 @@ +function getRandomIndex (length) { + return Math.floor(Math.random() * length) +} + +/** + * Initialize your data structure here. + */ +const RandomizedSet = function () { + this.values = [] + this.valuesIndexMap = new Map() +} + +/** + * Inserts a value to the set. Returns true if the set did not already contain the specified element. + * @param {number} val + * @return {boolean} + */ +RandomizedSet.prototype.insert = function (val) { + if (this.valuesIndexMap.has(val)) { + return false + } + + this.values.push(val) + this.valuesIndexMap.set(val, this.values.length - 1) + return true +} + +/** + * Removes a value from the set. Returns true if the set contained the specified element. + * @param {number} val + * @return {boolean} + */ +RandomizedSet.prototype.remove = function (val) { + if (!this.valuesIndexMap.has(val)) { + return false + } + + const valIndex = this.valuesIndexMap.get(val) + const lastVal = this.values.pop() + + if (val !== lastVal) { + this.values[valIndex] = lastVal + this.valuesIndexMap.set(lastVal, valIndex) + } + + this.valuesIndexMap.delete(val) + return true +} + +/** + * Get a random element from the set. + * @return {number} + */ +RandomizedSet.prototype.getRandom = function () { + const index = getRandomIndex(this.values.length) + return this.values[index] +} + +/** + * Your RandomizedSet object will be instantiated and called as such: + * var obj = new RandomizedSet() + * var param_1 = obj.insert(val) + * var param_2 = obj.remove(val) + * var param_3 = obj.getRandom() + */ + +module.exports = RandomizedSet diff --git a/problems/insert-delete-getrandom-o1/RandomizedSet.test.js b/problems/insert-delete-getrandom-o1/RandomizedSet.test.js new file mode 100644 index 0000000..5663514 --- /dev/null +++ b/problems/insert-delete-getrandom-o1/RandomizedSet.test.js @@ -0,0 +1,33 @@ +const RandomizedSet = require('./RandomizedSet') + +test('Example 1', () => { + const randomSet = new RandomizedSet() + + expect(randomSet.insert(1)).toBe(true) + + expect(randomSet.remove(2)).toBe(false) + + expect(randomSet.insert(2)).toBe(true) + + randomSet.getRandom() + + expect(randomSet.remove(1)).toBe(true) + + expect(randomSet.insert(2)).toBe(false) + + expect(randomSet.getRandom()).toBe(2) +}) + +test('remove the one and only value.', () => { + const randomSet = new RandomizedSet() + + expect(randomSet.remove(0)).toBe(false) + + expect(randomSet.insert(0)).toBe(true) + + expect(randomSet.remove(0)).toBe(true) + + expect(randomSet.insert(0)).toBe(true) + + expect(randomSet.getRandom()).toBe(0) +}) diff --git a/problems/invert-binary-tree/README.md b/problems/invert-binary-tree/README.md new file mode 100644 index 0000000..6d6575f --- /dev/null +++ b/problems/invert-binary-tree/README.md @@ -0,0 +1,43 @@ +# Invert Binary Tree + +LeetCode #: [226](https://leetcode.com/problems/invert-binary-tree/) + +Difficulty: Easy + +Topics: Tree. + +## Problem + +Invert a binary tree. + +Example: + +```text +Input: + + 4 + / \ + 2 7 + / \ / \ +1 3 6 9 + +Output: + + 4 + / \ + 7 2 + / \ / \ +9 6 3 1 +``` + +Trivia: + +This problem was inspired by [this original tweet](https://twitter.com/mxcl/status/608682016205344768) by [Max Howell](https://twitter.com/mxcl): + +> Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so f*** off. + +## Complexity Analysis + +Time complexity: O(n). + +Space complexity: O(h) where h is the height of the tree. O(n) in the worst case when h is equal to n. diff --git a/problems/invert-binary-tree/invertTree.js b/problems/invert-binary-tree/invertTree.js new file mode 100644 index 0000000..9673292 --- /dev/null +++ b/problems/invert-binary-tree/invertTree.js @@ -0,0 +1,27 @@ +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {TreeNode} + */ +const invertTree = function (root) { + if (root === null) { + return null + } + + const right = invertTree(root.left) + const left = invertTree(root.right) + + root.left = left + root.right = right + + return root +} + +module.exports = invertTree diff --git a/problems/invert-binary-tree/invertTree.test.js b/problems/invert-binary-tree/invertTree.test.js new file mode 100644 index 0000000..ff3c8a3 --- /dev/null +++ b/problems/invert-binary-tree/invertTree.test.js @@ -0,0 +1,10 @@ +const { deserialize, serialize } = require('leettree') +const invertTree = require('./invertTree') + +test('Example 1', () => { + const root = deserialize([4, 2, 7, 1, 3, 6, 9]) + + const result = invertTree(root) + + expect(serialize(result)).toStrictEqual([4, 7, 2, 9, 6, 3, 1]) +}) diff --git a/problems/largest-divisible-subset/README.md b/problems/largest-divisible-subset/README.md new file mode 100644 index 0000000..297febb --- /dev/null +++ b/problems/largest-divisible-subset/README.md @@ -0,0 +1,34 @@ +# Largest Divisible Subset + +LeetCode #: [368](https://leetcode.com/problems/largest-divisible-subset/) + +Difficulty: Medium + +Topics: Math, Dynamic Programming. + +## Problem + +Given a set of distinct positive integers, find the largest subset such that every pair `(S[i], S[j])` of elements in this subset satisfies: + +`S[i] % S[j] = 0` or `S[j] % S[i] = 0.` + +If there are multiple solutions, return any subset is fine. + +Example 1: + +```text +Input: [1,2,3] +Output: [1,2] (of course, [1,3] will also be ok) +``` + +Example 2: + +```text +Input: [1,2,4,8] +Output: [1,2,4,8] +``` + +## Solution Explanation + +Reference: [4 lines in Python](https://leetcode.com/problems/largest-divisible-subset/discuss/84002/4-lines-in-Python) by [ +StefanPochmann](https://leetcode.com/stefanpochmann) diff --git a/problems/largest-divisible-subset/largestDivisibleSubset.js b/problems/largest-divisible-subset/largestDivisibleSubset.js new file mode 100644 index 0000000..c59bf0d --- /dev/null +++ b/problems/largest-divisible-subset/largestDivisibleSubset.js @@ -0,0 +1,30 @@ +const maxBy = require('lodash/maxBy') + +/** + * @param {number[]} nums + * @return {number[]} + */ +const largestDivisibleSubset = function (nums) { + const numSubsetMap = new Map() + numSubsetMap.set(-1, []) + nums.sort((a, b) => a - b) + + for (const num of nums) { + let largestSubset = [] + + for (const [key, value] of numSubsetMap.entries()) { + if (num % key === 0 && value.length > largestSubset.length) { + largestSubset = [...value] + } + } + + largestSubset.push(num) + numSubsetMap.set(num, largestSubset) + } + + const result = maxBy([...numSubsetMap.values()], (s) => s.length) + + return result +} + +module.exports = largestDivisibleSubset diff --git a/problems/largest-divisible-subset/largestDivisibleSubset.test.js b/problems/largest-divisible-subset/largestDivisibleSubset.test.js new file mode 100644 index 0000000..4541a58 --- /dev/null +++ b/problems/largest-divisible-subset/largestDivisibleSubset.test.js @@ -0,0 +1,17 @@ +const largestDivisibleSubset = require('./largestDivisibleSubset') + +test('Example 1', () => { + const nums = [1, 2, 3] + + const result = largestDivisibleSubset(nums) + + expect(result).toStrictEqual([1, 2]) +}) + +test('Example 2', () => { + const nums = [1, 2, 4, 8] + + const result = largestDivisibleSubset(nums) + + expect(result).toStrictEqual([1, 2, 4, 8]) +}) diff --git a/problems/longest-duplicate-substring/README.md b/problems/longest-duplicate-substring/README.md new file mode 100644 index 0000000..8b4e0c9 --- /dev/null +++ b/problems/longest-duplicate-substring/README.md @@ -0,0 +1,36 @@ +# Longest Duplicate Substring + +LeetCode #: [1044](https://leetcode.com/problems/longest-duplicate-substring/) + +Difficulty: Hard + +Topics: Hash Table, Binary Search. + +## Problem + +Given a string `S`, consider all duplicated substrings: (contiguous) substrings of S that occur 2 or more times. (The occurrences may overlap.) + +Return any duplicated substring that has the longest possible length. (If `S` does not have a duplicated substring, the answer is `""`.) + +Example 1: + +```text +Input: "banana" +Output: "ana" +``` + +Example 2: + +```text +Input: "abcd" +Output: "" +``` + +Note: + +- `2 <= S.length <= 10^5` +- `S` consists of lowercase English letters. + +## Solution Explanation + +Reference: [[Python] Binary Search](https://leetcode.com/problems/longest-duplicate-substring/discuss/290871/Python-Binary-Search) by [lee215](https://leetcode.com/problems/longest-duplicate-substring/discuss/290871/Python-Binary-Search) diff --git a/problems/longest-duplicate-substring/longestDupSubstring.js b/problems/longest-duplicate-substring/longestDupSubstring.js new file mode 100644 index 0000000..dc35152 --- /dev/null +++ b/problems/longest-duplicate-substring/longestDupSubstring.js @@ -0,0 +1,68 @@ +const mod = 2 ** 32 + +const getHash = (chars) => { + let hash = 0 + for (let i = 0; i < chars.length; i++) { + hash = (hash * 26 + chars[i]) % mod + } + + return hash +} + +const mathPowMod = (num, pow, mod) => { + let result = 1 + for (let i = 1; i <= pow; i++) { + result = (result * num) % mod + } + + return result +} + +/** + * @param {string} S + * @return {string} + */ +const longestDupSubstring = function (S) { + const chars = S.split('').map(s => { + return s.charCodeAt(0) - 'a'.charCodeAt(0) + }) + + const test = (length) => { + // compute the hash of string S[0:idx] + let hash = getHash(chars.slice(0, length)) + + // Already seen hashes of strings of length L (len) + const seen = new Set([hash]) + + const p = mathPowMod(26, length, mod) + for (let i = length; i < chars.length; i++) { + // Compute rolling hash in O(1) time + // hash = (hash * 26 + chars[i] - chars[i - length] * p); // without mod + hash = ((hash * 26 + chars[i] - chars[i - length] * p % mod + mod) % mod) + + if (seen.has(hash)) { + return i - length + 1 + } + + seen.add(hash) + } + } + + let res = 0 + let lo = 0 + let hi = chars.length + while (lo < hi) { + const mid = Math.floor((lo + hi + 1) / 2) + const pos = test(mid) + if (pos) { + lo = mid + res = pos + } else { + hi = mid - 1 + } + } + + return S.substring(res, res + lo) +} + +module.exports = longestDupSubstring diff --git a/problems/longest-duplicate-substring/longestDupSubstring.test.js b/problems/longest-duplicate-substring/longestDupSubstring.test.js new file mode 100644 index 0000000..a21ff07 --- /dev/null +++ b/problems/longest-duplicate-substring/longestDupSubstring.test.js @@ -0,0 +1,25 @@ +const longestDupSubstring = require('./longestDupSubstring') + +test('Example 1', () => { + const S = 'banana' + + const result = longestDupSubstring(S) + + expect(result).toBe('ana') +}) + +test('Example 2', () => { + const S = 'abcd' + + const result = longestDupSubstring(S) + + expect(result).toBe('') +}) + +test('Long string', () => { + const S = 'moplvidmaagmsiyyrkchbyhivlqwqsjcgtumqscmxrxrvwsnjjvygrelcbjgbpounhuyealllginkitfaiviraqcycjmskrozcdqylbuejrgfnquercvghppljmojfvylcxakyjxnampmakyjbqgwbyokaybcuklkaqzawageypfqhhasetugatdaxpvtevrigynxbqodiyioapgxqkndujeranxgebnpgsukybyowbxhgpkwjfdywfkpufcxzzqiuglkakibbkobonunnzwbjktykebfcbobxdflnyzngheatpcvnhdwkkhnlwnjdnrmjaevqopvinnzgacjkbhvsdsvuuwwhwesgtdzuctshytyfugdqswvxisyxcxoihfgzxnidnfadphwumtgdfmhjkaryjxvfquucltmuoosamjwqqzeleaiplwcbbxjxxvgsnonoivbnmiwbnijkzgoenohqncjqnckxbhpvreasdyvffrolobxzrmrbvwkpdbfvbwwyibydhndmpvqyfmqjwosclwxhgxmwjiksjvsnwupraojuatksjfqkvvfroqxsraskbdbgtppjrnzpfzabmcczlwynwomebvrihxugvjmtrkzdwuafozjcfqacenabmmxzcueyqwvbtslhjeiopgbrbvfbnpmvlnyexopoahgmwplwxnxqzhucdieyvbgtkfmdeocamzenecqlbhqmdfrvpsqyxvkkyfrbyolzvcpcbkdprttijkzcrgciidavsmrczbollxbkytqjwbiupvsorvkorfriajdtsowenhpmdtvamkoqacwwlkqfdzorjtepwlemunyrghwlvjgaxbzawmikfhtaniwviqiaeinbsqidetfsdbgsydkxgwoqyztaqmyeefaihmgrbxzyheoegawthcsyyrpyvnhysynoaikwtvmwathsomddhltxpeuxettpbeftmmyrqclnzwljlpxazrzzdosemwmthcvgwtxtinffopqxbufjwsvhqamxpydcnpekqhsovvqugqhbgweaiheeicmkdtxltkalexbeftuxvwnxmqqjeyourvbdfikqnzdipmmmiltjapovlhkpunxljeutwhenrxyfeufmzipqvergdkwptkilwzdxlydxbjoxjzxwcfmznfqgoaemrrxuwpfkftwejubxkgjlizljoynvidqwxnvhngqakmmehtvykbjwrrrjvwnrteeoxmtygiiygynedvfzwkvmffghuduspyyrnftyvsvjstfohwwyxhmlfmwguxxzgwdzwlnnltpjvnzswhmbzgdwzhvbgkiddhirgljbflgvyksxgnsvztcywpvutqryzdeerlildbzmtsgnebvsjetdnfgikrbsktbrdamfccvcptfaaklmcaqmglneebpdxkvcwwpndrjqnpqgbgihsfeotgggkdbvcdwfjanvafvxsvvhzyncwlmqqsmledzfnxxfyvcmhtjreykqlrfiqlsqzraqgtmocijejneeezqxbtomkwugapwesrinfiaxwxradnuvbyssqkznwwpsbgatlsxfhpcidfgzrc' + + const result = longestDupSubstring(S) + + expect(result).toBe('akyj') +}) diff --git a/problems/perfect-squares/README.md b/problems/perfect-squares/README.md new file mode 100644 index 0000000..0b8cfde --- /dev/null +++ b/problems/perfect-squares/README.md @@ -0,0 +1,33 @@ +# Perfect Squares + +LeetCode #: [279](https://leetcode.com/problems/perfect-squares/) + +Difficulty: Medium + +Topics: Math, Dynamic Programming, Breadth-first Search. + +## Problem + +Given a positive integer n, find the least number of perfect square numbers (for example, `1, 4, 9, 16, ...`) which sum to n. + +Example 1: + +```text +Input: n = 12 +Output: 3 +Explanation: 12 = 4 + 4 + 4. +``` + +Example 2: + +```text +Input: n = 13 +Output: 2 +Explanation: 13 = 4 + 9. +``` + +## Solution Explanation + +The solution uses the [Lagrange's four-square theorem](https://en.wikipedia.org/wiki/Lagrange%27s_four-square_theorem). + +Reference: [Summary of 4 different solutions (BFS, DP, static DP and mathematics)](https://leetcode.com/problems/perfect-squares/discuss/71488/Summary-of-4-different-solutions-(BFS-DP-static-DP-and-mathematics)) by [zhukov](https://leetcode.com/zhukov) diff --git a/problems/perfect-squares/numSquares.js b/problems/perfect-squares/numSquares.js new file mode 100644 index 0000000..3e9e220 --- /dev/null +++ b/problems/perfect-squares/numSquares.js @@ -0,0 +1,33 @@ +const isSquare = (n) => { + const sqrt = Math.floor(Math.sqrt(n)) + return ((sqrt * sqrt) === n) +} + +/** + * @param {number} n + * @return {number} + */ +const numSquares = function (n) { + if (isSquare(n)) { + return 1 + } + + while ((n & 3) === 0) { + n >>= 2 + } + + if ((n & 7) === 7) { + return 4 + } + + const sqrt = Math.sqrt(n) + for (let i = 1; i <= sqrt; i++) { + if (isSquare(n - (i * i))) { + return 2 + } + } + + return 3 +} + +module.exports = numSquares diff --git a/problems/perfect-squares/numSquares.test.js b/problems/perfect-squares/numSquares.test.js new file mode 100644 index 0000000..c4cbe6e --- /dev/null +++ b/problems/perfect-squares/numSquares.test.js @@ -0,0 +1,33 @@ +const numSquares = require('./numSquares') + +test('Example 1', () => { + const n = 12 + + const result = numSquares(n) + + expect(result).toBe(3) +}) + +test('Example 2', () => { + const n = 13 + + const result = numSquares(n) + + expect(result).toBe(2) +}) + +test('n is a squared number', () => { + const n = 4 + + const result = numSquares(n) + + expect(result).toBe(1) +}) + +test('15 (4+9+1+1) returns 4', () => { + const n = 15 + + const result = numSquares(n) + + expect(result).toBe(4) +}) diff --git a/problems/permutation-sequence/README.md b/problems/permutation-sequence/README.md new file mode 100644 index 0000000..810adde --- /dev/null +++ b/problems/permutation-sequence/README.md @@ -0,0 +1,41 @@ +# Permutation Sequence + +LeetCode #: [60](https://leetcode.com/problems/permutation-sequence/) + +Difficulty: Medium + +Topics: Math, Backtracking. + +## Problem + +The set `[1,2,3,...,n]` contains a total of n! unique permutations. + +By listing and labeling all of the permutations in order, we get the following sequence for n = 3: + +1. `"123"` +2. `"132"` +3. `"213"` +4. `"231"` +5. `"312"` +6. `"321"` + +Given n and k, return the k-th permutation sequence. + +Note: + +- Given n will be between 1 and 9 inclusive. +- Given k will be between 1 and n! inclusive. + +Example 1: + +```text +Input: n = 3, k = 3 +Output: "213" +``` + +Example 2: + +```text +Input: n = 4, k = 9 +Output: "2314" +``` diff --git a/problems/permutation-sequence/getPermutation.js b/problems/permutation-sequence/getPermutation.js new file mode 100644 index 0000000..69b0ee2 --- /dev/null +++ b/problems/permutation-sequence/getPermutation.js @@ -0,0 +1,27 @@ +/** + * @param {number} n + * @param {number} k + * @return {string} + */ +const getPermutation = function (n, k) { + const nums = [] + let fact = 1 + for (let i = 1; i <= n; i++) { + fact *= i + nums.push(i) + } + + let s = '' + let l = k - 1 + for (let i = 0; i < n; i++) { + fact /= (n - i) + const index = Math.floor(l / fact) + const deleted = nums.splice(index, 1) + s += deleted + l -= (index * fact) + } + + return s +} + +module.exports = getPermutation diff --git a/problems/permutation-sequence/getPermutation.test.js b/problems/permutation-sequence/getPermutation.test.js new file mode 100644 index 0000000..0f4afd1 --- /dev/null +++ b/problems/permutation-sequence/getPermutation.test.js @@ -0,0 +1,19 @@ +const getPermutation = require('./getPermutation') + +test('Example 1', () => { + const n = 3 + const k = 3 + + const result = getPermutation(n, k) + + expect(result).toBe('213') +}) + +test('Example 2', () => { + const n = 4 + const k = 9 + + const result = getPermutation(n, k) + + expect(result).toBe('2314') +}) diff --git a/problems/power-of-two/README.md b/problems/power-of-two/README.md new file mode 100644 index 0000000..26bf3bf --- /dev/null +++ b/problems/power-of-two/README.md @@ -0,0 +1,40 @@ +# Power of Two + +LeetCode #: [231](https://leetcode.com/problems/power-of-two/) + +Difficulty: Easy + +Topics: Math, Bit Manipulation. + +## Problem + +Given an integer, write a function to determine if it is a power of two. + +Example 1: + +```text +Input: 1 +Output: true +Explanation: 2^0 = 1 +``` + +Example 2: + +```text +Input: 16 +Output: true +Explanation: 2^4 = 16 +``` + +Example 3: + +```text +Input: 218 +Output: false +``` + +## Complexity Analysis + +Time complexity: O(1) + +Space complexity: O(1) diff --git a/problems/power-of-two/isPowerOfTwo.js b/problems/power-of-two/isPowerOfTwo.js new file mode 100644 index 0000000..b69d4e5 --- /dev/null +++ b/problems/power-of-two/isPowerOfTwo.js @@ -0,0 +1,12 @@ +/** + * @param {number} n + * @return {boolean} + */ +const isPowerOfTwo = function (n) { + return ( + n > 0 && + (n & (n - 1)) === 0 + ) +} + +module.exports = isPowerOfTwo diff --git a/problems/power-of-two/isPowerOfTwo.test.js b/problems/power-of-two/isPowerOfTwo.test.js new file mode 100644 index 0000000..9aa3e10 --- /dev/null +++ b/problems/power-of-two/isPowerOfTwo.test.js @@ -0,0 +1,25 @@ +const isPowerOfTwo = require('./isPowerOfTwo') + +test('Example 1', () => { + const n = 1 + + const result = isPowerOfTwo(n) + + expect(result).toBe(true) +}) + +test('Example 2', () => { + const n = 16 + + const result = isPowerOfTwo(n) + + expect(result).toBe(true) +}) + +test('Example 3', () => { + const n = 218 + + const result = isPowerOfTwo(n) + + expect(result).toBe(false) +}) diff --git a/problems/queue-reconstruction-by-height/README.md b/problems/queue-reconstruction-by-height/README.md new file mode 100644 index 0000000..3e699f5 --- /dev/null +++ b/problems/queue-reconstruction-by-height/README.md @@ -0,0 +1,25 @@ +# Queue Reconstruction by Height + +LeetCode #: [406](https://leetcode.com/problems/queue-reconstruction-by-height/) + +Difficulty: Medium + +Topics: Greedy. + +## Problem + +Suppose you have a random list of people standing in a queue. Each person is described by a pair of integers `(h, k)`, where `h` is the height of the person and `k` is the number of people in front of this person who have a height greater than or equal to `h`. Write an algorithm to reconstruct the queue. + +Note: + +The number of people is less than 1,100. + +Example + +```text +Input: +[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] + +Output: +[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] +``` diff --git a/problems/queue-reconstruction-by-height/reconstructQueue.js b/problems/queue-reconstruction-by-height/reconstructQueue.js new file mode 100644 index 0000000..600a558 --- /dev/null +++ b/problems/queue-reconstruction-by-height/reconstructQueue.js @@ -0,0 +1,22 @@ +const sortBy = require('lodash/sortBy') + +/** + * @param {number[][]} people + * @return {number[][]} + */ +const reconstructQueue = function (people) { + const sorted = sortBy(people, [ + (o) => -o[0], + (o) => o[1] + ]) + + const result = [] + for (let i = 0; i < sorted.length; i++) { + const [, idx] = sorted[i] + result.splice(idx, 0, sorted[i]) + } + + return result +} + +module.exports = reconstructQueue diff --git a/problems/queue-reconstruction-by-height/reconstructQueue.test.js b/problems/queue-reconstruction-by-height/reconstructQueue.test.js new file mode 100644 index 0000000..4d9a7ef --- /dev/null +++ b/problems/queue-reconstruction-by-height/reconstructQueue.test.js @@ -0,0 +1,9 @@ +const reconstructQueue = require('./reconstructQueue') + +test('Example 1', () => { + const people = [[7, 0], [4, 4], [7, 1], [5, 0], [6, 1], [5, 2]] + + const result = reconstructQueue(people) + + expect(result).toStrictEqual([[5, 0], [7, 0], [5, 2], [6, 1], [4, 4], [7, 1]]) +}) diff --git a/problems/random-pick-with-weight/README.md b/problems/random-pick-with-weight/README.md new file mode 100644 index 0000000..b57caac --- /dev/null +++ b/problems/random-pick-with-weight/README.md @@ -0,0 +1,39 @@ +# Random Pick with Weight + +LeetCode #: [528](https://leetcode.com/problems/random-pick-with-weight/) + +Difficulty: Medium + +Topic: Binary Search, Random. + +## Problem + +Given an array `w` of positive integers, where `w[i]` describes the weight of index `i`, write a function `pickIndex` which randomly picks an index in proportion to its weight. + +Note: + +- `1 <= w.length <= 10000` +- `1 <= w[i] <= 10^5` +- `pickIndex` will be called at most `10000` times. + +Example 1: + +```text +Input: +["Solution","pickIndex"] +[[[1]],[]] +Output: [null,0] +``` + +Example 2: + +```text +Input: +["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"] +[[[1,3]],[],[],[],[],[]] +Output: [null,0,1,1,1,0] +``` + +Explanation of Input Syntax: + +The input is two lists: the subroutines called and their arguments. `Solution`'s constructor has one argument, the array `w`. `pickIndex` has no arguments. Arguments are always wrapped with a list, even if there aren't any. diff --git a/problems/random-pick-with-weight/Solution.js b/problems/random-pick-with-weight/Solution.js new file mode 100644 index 0000000..2bbdd3e --- /dev/null +++ b/problems/random-pick-with-weight/Solution.js @@ -0,0 +1,34 @@ +const sortedLastIndex = require('lodash/sortedLastIndex') + +const getRandomInt = (max) => { + return Math.floor(Math.random() * max) +} + +/** + * @param {number[]} w + */ +const Solution = function (w) { + this.all = [] + let sum = 0 + for (let i = 0; i < w.length; i++) { + sum += w[i] + this.all.push(sum) + } +} + +/** + * @return {number} + */ +Solution.prototype.pickIndex = function () { + const random = getRandomInt(this.all[this.all.length - 1]) + const index = sortedLastIndex(this.all, random) + return index +} + +/** + * Your Solution object will be instantiated and called as such: + * var obj = new Solution(w) + * var param_1 = obj.pickIndex() + */ + +module.exports = Solution diff --git a/problems/random-pick-with-weight/Solution.test.js b/problems/random-pick-with-weight/Solution.test.js new file mode 100644 index 0000000..94fca03 --- /dev/null +++ b/problems/random-pick-with-weight/Solution.test.js @@ -0,0 +1,6 @@ +const Solution = require('./Solution') + +test('Example 1', () => { + const solution = new Solution([1]) + expect(solution.pickIndex()).toBe(0) +}) diff --git a/problems/reconstruct-itinerary/README.md b/problems/reconstruct-itinerary/README.md new file mode 100644 index 0000000..9d8b7ae --- /dev/null +++ b/problems/reconstruct-itinerary/README.md @@ -0,0 +1,39 @@ +# Reconstruct Itinerary + +LeetCode #: [332](https://leetcode.com/problems/reconstruct-itinerary/) + +Difficulty: Medium + +Topic: Depth-first Search, Graph. + +## Problem + +Given a list of airline tickets represented by pairs of departure and arrival airports `[from, to]`, reconstruct the itinerary in order. All of the tickets belong to a man who departs from `JFK`. Thus, the itinerary must begin with `JFK`. + +Note: + +- If there are multiple valid itineraries, you should return the itinerary that has the smallest lexical order when read as a single string. For example, the itinerary `["JFK", "LGA"]` has a smaller lexical order than `["JFK", "LGB"]`. +- All airports are represented by three capital letters (IATA code). +- You may assume all tickets form at least one valid itinerary. +- One must use all the tickets once and only once. + +Example 1: + +```text +Input: [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]] +Output: ["JFK", "MUC", "LHR", "SFO", "SJC"] +``` + +Example 2: + +```text +Input: [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]] +Output: ["JFK","ATL","JFK","SFO","ATL","SFO"] +Explanation: +Another possible reconstruction is ["JFK","SFO","ATL","JFK","ATL","SFO"]. +But it is larger in lexical order. +``` + +## Solution Explanation + +Reference: [Short Ruby / Python / Java / C++](https://leetcode.com/problems/reconstruct-itinerary/discuss/78768/Short-Ruby-Python-Java-C%2B%2B) by [stefanpochmann](https://leetcode.com/stefanpochmann) diff --git a/problems/reconstruct-itinerary/findItinerary.js b/problems/reconstruct-itinerary/findItinerary.js new file mode 100644 index 0000000..6c82fb3 --- /dev/null +++ b/problems/reconstruct-itinerary/findItinerary.js @@ -0,0 +1,52 @@ +const sortedIndex = require('lodash/sortedIndex') + +const getFromTosMap = (tickets) => { + const fromTosMap = new Map() + + for (const [from, to] of tickets) { + const tos = fromTosMap.get(from) || [] + const index = sortedIndex(tos, to) + tos.splice(index, 0, to) + fromTosMap.set(from, tos) + } + + return fromTosMap +} + +const buildItinerary = (fromTosMap) => { + const itinerary = [] + const stack = [] + stack.push('JFK') + + while (stack.length > 0) { + let from = stack[stack.length - 1] + + while ( + fromTosMap.has(from) && + fromTosMap.get(from).length > 0 + ) { + const tos = fromTosMap.get(from) + const dest = tos.shift() + stack.push(dest) + + from = dest + } + + const dest = stack.pop() + itinerary.unshift(dest) + } + + return itinerary +} + +/** + * @param {string[][]} tickets + * @return {string[]} + */ +const findItinerary = function (tickets) { + const fromTosMap = getFromTosMap(tickets) + const itinerary = buildItinerary(fromTosMap) + return itinerary +} + +module.exports = findItinerary diff --git a/problems/reconstruct-itinerary/findItinerary.test.js b/problems/reconstruct-itinerary/findItinerary.test.js new file mode 100644 index 0000000..8ceef60 --- /dev/null +++ b/problems/reconstruct-itinerary/findItinerary.test.js @@ -0,0 +1,25 @@ +const findItinerary = require('./findItinerary') + +test('Example 1', () => { + const tickets = [['MUC', 'LHR'], ['JFK', 'MUC'], ['SFO', 'SJC'], ['LHR', 'SFO']] + + const result = findItinerary(tickets) + + expect(result).toStrictEqual(['JFK', 'MUC', 'LHR', 'SFO', 'SJC']) +}) + +test('Example 2', () => { + const tickets = [['JFK', 'SFO'], ['JFK', 'ATL'], ['SFO', 'ATL'], ['ATL', 'JFK'], ['ATL', 'SFO']] + + const result = findItinerary(tickets) + + expect(result).toStrictEqual(['JFK', 'ATL', 'JFK', 'SFO', 'ATL', 'SFO']) +}) + +test('JFK->NRT first instead of JFK->KUL', () => { + const tickets = [['JFK', 'KUL'], ['JFK', 'NRT'], ['NRT', 'JFK']] + + const result = findItinerary(tickets) + + expect(result).toStrictEqual(['JFK', 'NRT', 'JFK', 'KUL']) +}) diff --git a/problems/reverse-string/README.md b/problems/reverse-string/README.md new file mode 100644 index 0000000..1b87740 --- /dev/null +++ b/problems/reverse-string/README.md @@ -0,0 +1,35 @@ +# Reverse String + +LeetCode #: [344](https://leetcode.com/problems/reverse-string/) + +Difficulty: Easy + +Topic: Two Pointers, String. + +## Problem + +Write a function that reverses a string. The input string is given as an array of characters `char[]`. + +Do not allocate extra space for another array, you must do this by modifying the input array [in-place](https://en.wikipedia.org/wiki/In-place_algorithm) with O(1) extra memory. + +You may assume all the characters consist of [printable ascii characters](https://en.wikipedia.org/wiki/ASCII#Printable_characters). + +Example 1: + +```text +Input: ["h","e","l","l","o"] +Output: ["o","l","l","e","h"] +``` + +Example 2: + +```text +Input: ["H","a","n","n","a","h"] +Output: ["h","a","n","n","a","H"] +``` + +## Complexity Analysis + +Time complexity: O(n). + +Space complexity: O(1) diff --git a/problems/reverse-string/reverseString.js b/problems/reverse-string/reverseString.js new file mode 100644 index 0000000..9a58036 --- /dev/null +++ b/problems/reverse-string/reverseString.js @@ -0,0 +1,12 @@ +/** + * @param {character[]} s + * @return {void} Do not return anything, modify s in-place instead. + */ +const reverseString = function (s) { + for (let i = 0; i < s.length / 2; i++) { + const j = s.length - 1 - i + ;[s[i], s[j]] = [s[j], s[i]] + } +} + +module.exports = reverseString diff --git a/problems/reverse-string/reverseString.test.js b/problems/reverse-string/reverseString.test.js new file mode 100644 index 0000000..654e8ae --- /dev/null +++ b/problems/reverse-string/reverseString.test.js @@ -0,0 +1,17 @@ +const reverseString = require('./reverseString') + +test('Example 1', () => { + const s = ['h', 'e', 'l', 'l', 'o'] + + reverseString(s) + + expect(s).toStrictEqual(['o', 'l', 'l', 'e', 'h']) +}) + +test('Example 2', () => { + const s = ['H', 'a', 'n', 'n', 'a', 'h'] + + reverseString(s) + + expect(s).toStrictEqual(['h', 'a', 'n', 'n', 'a', 'H']) +}) diff --git a/problems/search-in-a-binary-search-tree/README.md b/problems/search-in-a-binary-search-tree/README.md new file mode 100644 index 0000000..b9ba118 --- /dev/null +++ b/problems/search-in-a-binary-search-tree/README.md @@ -0,0 +1,42 @@ +# Search in a Binary Search Tree + +LeetCode #: [700](https://leetcode.com/problems/search-in-a-binary-search-tree/) + +Difficulty: Easy + +Topics: Tree. + +## Problem + +Given the root node of a binary search tree (BST) and a value. You need to find the node in the BST that the node's value equals the given value. Return the subtree rooted with that node. If such node doesn't exist, you should return NULL. + +For example, + +```text +Given the tree: + 4 + / \ + 2 7 + / \ + 1 3 + +And the value to search: 2 +``` + +You should return this subtree: + +```text + 2 + / \ + 1 3 +``` + +In the example above, if we want to search the value `5`, since there is no node with value `5`, we should return `NULL`. + +Note that an empty tree is represented by `NULL`, therefore you would see the expected output (serialized tree format) as `[]`, not `null`. + +## Complexity Analysis + +Time complexity: O(n) where n is the number of tree nodes. + +Space complexity: O(h) where h is the height of the tree due to the recursive call stack in depth-first search. diff --git a/problems/search-in-a-binary-search-tree/searchBST.js b/problems/search-in-a-binary-search-tree/searchBST.js new file mode 100644 index 0000000..d7767aa --- /dev/null +++ b/problems/search-in-a-binary-search-tree/searchBST.js @@ -0,0 +1,26 @@ +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @param {number} val + * @return {TreeNode} + */ +const searchBST = function (root, val) { + if (root === null) { + return null + } + + if (root.val === val) { + return root + } + + return searchBST(root.left, val) || searchBST(root.right, val) +} + +module.exports = searchBST diff --git a/problems/search-in-a-binary-search-tree/searchBST.test.js b/problems/search-in-a-binary-search-tree/searchBST.test.js new file mode 100644 index 0000000..e857142 --- /dev/null +++ b/problems/search-in-a-binary-search-tree/searchBST.test.js @@ -0,0 +1,21 @@ +const { deserialize } = require('leettree') +const serialize = require('./serialize') +const searchBST = require('./searchBST') + +test('Example 1', () => { + const root = deserialize([4, 2, 7, 1, 3]) + const val = 2 + + const result = searchBST(root, val) + + expect(serialize(result)).toStrictEqual([2, 1, 3]) +}) + +test('node does not exist', () => { + const root = deserialize([4, 2, 7, 1, 3]) + const val = 5 + + const result = searchBST(root, val) + + expect(serialize(result)).toStrictEqual([]) +}) diff --git a/problems/search-in-a-binary-search-tree/serialize.js b/problems/search-in-a-binary-search-tree/serialize.js new file mode 100644 index 0000000..b2bdccc --- /dev/null +++ b/problems/search-in-a-binary-search-tree/serialize.js @@ -0,0 +1,41 @@ +/** + * Serialize a binary tree to array. + * + * @param {TreeNode|Object} tree - Binary tree. + * @returns {Array} Array representation of the binary tree. + */ +const serialize = (tree) => { + if (!tree) { + return [] + } + + const result = [tree.val] + const nodeQueue = [tree] + let lastValueIndex = 0 + + while (nodeQueue.length > 0) { + const node = nodeQueue.shift() + + if (node.left === null || node.left === undefined) { + result.push(null) + } else { + result.push(node.left.val) + nodeQueue.push(node.left) + lastValueIndex = result.length - 1 + } + + if (node.right === null || node.right === undefined) { + result.push(null) + } else { + result.push(node.right.val) + nodeQueue.push(node.right) + lastValueIndex = result.length - 1 + } + } + + result.splice(lastValueIndex + 1) + + return result +} + +module.exports = serialize diff --git a/problems/single-number-ii/README.md b/problems/single-number-ii/README.md new file mode 100644 index 0000000..6ba35bf --- /dev/null +++ b/problems/single-number-ii/README.md @@ -0,0 +1,43 @@ +# Single Number II + +LeetCode #: [137](https://leetcode.com/problems/single-number-ii/) + +Difficulty: Medium + +Topics: Bit Manipulation. + +## Problem + +Given a non-empty array of integers, every element appears three times except for one, which appears exactly once. Find that single one. + +Note: + +Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? + +Example 1: + +```text +Input: [2,2,3,2] +Output: 3 +``` + +Example 2: + +```text +Input: [0,1,0,1,0,1,99] +Output: 99 +``` + +## Solution Explanation + +Reference: + +- [Detailed explanation and generalization of the bitwise operation method for single numbers](https://leetcode.com/problems/single-number-ii/discuss/43295/Detailed-explanation-and-generalization-of-the-bitwise-operation-method-for-single-numbers) by [fun4LeetCode](https://leetcode.com/fun4leetcode) + +- [Challenge me , thx](https://leetcode.com/problems/single-number-ii/discuss/43294/Challenge-me-thx) by [againest1](https://leetcode.com/againest1) + +## Complexity Analysis + +Time complexity: O(n) + +Space complexiy: O(1) diff --git a/problems/single-number-ii/singleNumber.js b/problems/single-number-ii/singleNumber.js new file mode 100644 index 0000000..35a8b08 --- /dev/null +++ b/problems/single-number-ii/singleNumber.js @@ -0,0 +1,17 @@ +/** + * @param {number[]} nums + * @return {number} + */ +const singleNumber = function (nums) { + let bit1 = 0 + let bit2 = 0 + + for (let i = 0; i < nums.length; i++) { + bit1 = (bit1 ^ nums[i]) & ~bit2 + bit2 = (bit2 ^ nums[i]) & ~bit1 + } + + return bit1 +} + +module.exports = singleNumber diff --git a/problems/single-number-ii/singleNumber.test.js b/problems/single-number-ii/singleNumber.test.js new file mode 100644 index 0000000..8f4024b --- /dev/null +++ b/problems/single-number-ii/singleNumber.test.js @@ -0,0 +1,17 @@ +const singleNumber = require('./singleNumber') + +test('Example 1', () => { + const input = [2, 2, 3, 2] + + const result = singleNumber(input) + + expect(result).toBe(3) +}) + +test('Example 2', () => { + const input = [0, 1, 0, 1, 0, 1, 99] + + const result = singleNumber(input) + + expect(result).toBe(99) +}) diff --git a/problems/sort-colors/README.md b/problems/sort-colors/README.md new file mode 100644 index 0000000..c2e46af --- /dev/null +++ b/problems/sort-colors/README.md @@ -0,0 +1,38 @@ +# Sort Colors + +LeetCode #: [75](https://leetcode.com/problems/sort-colors/) + +Difficulty: Medium + +Topics: Array, Two Pointers, Sort. + +## Problem + +Given an array with n objects colored red, white or blue, sort them [in-place](https://en.wikipedia.org/wiki/In-place_algorithm) so that objects of the same color are adjacent, with the colors in the order red, white and blue. + +Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively. + +Note: You are not suppose to use the library's sort function for this problem. + +Example: + +```text +Input: [2,0,2,1,1,0] +Output: [0,0,1,1,2,2] +``` + +Follow up: + +- A rather straight forward solution is a two-pass algorithm using counting sort. + + First, iterate the array counting number of 0's, 1's, and 2's, then overwrite array with total number of 0's, then 1's and followed by 2's. + +- Could you come up with a one-pass algorithm using only constant space? + +## Solution Explanation + +There are two solutions for this problem. Both has O(n) one-pass time complexity and O(1) space complexity. + +1. [Array manipulation approach](array-manipulation-approach) which uses `splice`, `unshift` and `push` array methods. This approach performs faster on the LeetCode platform. + +2. [Pointers approach](pointers-approach) which uses multiple index pointers to perform array element swapping. diff --git a/problems/sort-colors/array-manipulation-approach/README.md b/problems/sort-colors/array-manipulation-approach/README.md new file mode 100644 index 0000000..98ba07d --- /dev/null +++ b/problems/sort-colors/array-manipulation-approach/README.md @@ -0,0 +1,7 @@ +# Sort Colors - Array Manipulation Approach + +## Complexity Analysis + +Time complexity: O(n) + +Space complexity: O(1) diff --git a/problems/sort-colors/array-manipulation-approach/sortColors.js b/problems/sort-colors/array-manipulation-approach/sortColors.js new file mode 100644 index 0000000..0338028 --- /dev/null +++ b/problems/sort-colors/array-manipulation-approach/sortColors.js @@ -0,0 +1,28 @@ +/** + * @param {number[]} nums + * @return {void} Do not return anything, modify nums in-place instead. + */ +const sortColors = function (nums) { + const total = nums.length + let count = 0 + let i = 0 + + while (count < total) { + const num = nums[i] + + if (num === 0) { + nums.splice(i, 1) + nums.unshift(num) + i++ + } else if (num === 2) { + nums.splice(i, 1) + nums.push(num) + } else { + i++ + } + + count++ + } +} + +module.exports = sortColors diff --git a/problems/sort-colors/array-manipulation-approach/sortColors.test.js b/problems/sort-colors/array-manipulation-approach/sortColors.test.js new file mode 100644 index 0000000..1071273 --- /dev/null +++ b/problems/sort-colors/array-manipulation-approach/sortColors.test.js @@ -0,0 +1,9 @@ +const frequencySort = require('./sortColors') + +test('Example 1', () => { + const nums = [2, 0, 2, 1, 1, 0] + + frequencySort(nums) + + expect(nums).toStrictEqual([0, 0, 1, 1, 2, 2]) +}) diff --git a/problems/sort-colors/pointers-approach/README.md b/problems/sort-colors/pointers-approach/README.md new file mode 100644 index 0000000..73d8126 --- /dev/null +++ b/problems/sort-colors/pointers-approach/README.md @@ -0,0 +1,7 @@ +# Sort Colors - Pointers Approach + +## Complexity Analysis + +Time complexity: O(n) + +Space complexity: O(1) diff --git a/problems/sort-colors/pointers-approach/sortColors.js b/problems/sort-colors/pointers-approach/sortColors.js new file mode 100644 index 0000000..e33505c --- /dev/null +++ b/problems/sort-colors/pointers-approach/sortColors.js @@ -0,0 +1,27 @@ +const sortColors = function (nums) { + let redPtr = 0 + while (nums[redPtr] === 0) { + redPtr++ + } + + let bluePtr = nums.length - 1 + while (nums[bluePtr] === 2) { + bluePtr-- + } + + let i = redPtr + 1 + while (i <= bluePtr) { + if (nums[i] === 0) { + [nums[i], nums[redPtr]] = [nums[redPtr], nums[i]] + redPtr++ + i = Math.max(redPtr, i) + } else if (nums[i] === 2) { + [nums[i], nums[bluePtr]] = [nums[bluePtr], nums[i]] + bluePtr-- + } else { + i++ + } + } +} + +module.exports = sortColors diff --git a/problems/sort-colors/pointers-approach/sortColors.test.js b/problems/sort-colors/pointers-approach/sortColors.test.js new file mode 100644 index 0000000..21a3ff1 --- /dev/null +++ b/problems/sort-colors/pointers-approach/sortColors.test.js @@ -0,0 +1,17 @@ +const frequencySort = require('./sortColors') + +test('Example 1', () => { + const nums = [2, 0, 2, 1, 1, 0] + + frequencySort(nums) + + expect(nums).toStrictEqual([0, 0, 1, 1, 2, 2]) +}) + +test('first element is already 0, last element is already 2', () => { + const nums = [0, 0, 1, 2, 1, 0, 1, 2, 2] + + frequencySort(nums) + + expect(nums).toStrictEqual([0, 0, 0, 1, 1, 1, 2, 2, 2]) +}) diff --git a/problems/sum-root-to-leaf-numbers/README.md b/problems/sum-root-to-leaf-numbers/README.md new file mode 100644 index 0000000..7e5c7e1 --- /dev/null +++ b/problems/sum-root-to-leaf-numbers/README.md @@ -0,0 +1,54 @@ +# Sum Root to Leaf Numbers + +LeetCode #: [129](https://leetcode.com/problems/sum-root-to-leaf-numbers/) + +Difficulty: Medium + +Topic: Tree, Depth-first Search. + +## Problem + +Given a binary tree containing digits from `0-9` only, each root-to-leaf path could represent a number. + +An example is the root-to-leaf path `1->2->3` which represents the number `123`. + +Find the total sum of all root-to-leaf numbers. + +Note: A leaf is a node with no children. + +Example: + +```text +Input: [1,2,3] + 1 + / \ + 2 3 +Output: 25 +Explanation: +The root-to-leaf path 1->2 represents the number 12. +The root-to-leaf path 1->3 represents the number 13. +Therefore, sum = 12 + 13 = 25. +``` + +Example 2: + +```text +Input: [4,9,0,5,1] + 4 + / \ + 9 0 + / \ +5 1 +Output: 1026 +Explanation: +The root-to-leaf path 4->9->5 represents the number 495. +The root-to-leaf path 4->9->1 represents the number 491. +The root-to-leaf path 4->0 represents the number 40. +Therefore, sum = 495 + 491 + 40 = 1026. +``` + +## Complexity Analysis + +Time complexity: O(n). We iterate through every node in the tree once. + +Space complexity: O(h) where h is the height of the tree. Space cost is the recursive stack size. diff --git a/problems/sum-root-to-leaf-numbers/sumNumbers.js b/problems/sum-root-to-leaf-numbers/sumNumbers.js new file mode 100644 index 0000000..4babf9e --- /dev/null +++ b/problems/sum-root-to-leaf-numbers/sumNumbers.js @@ -0,0 +1,40 @@ +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {number} + */ +const sumNumbers = function (root) { + let sum = 0 + let cur = 0 + + const process = (node) => { + if (!node) { + return + } + + cur = (cur * 10) + node.val + + if (!node.left && !node.right) { + sum += cur + cur = (cur - node.val) / 10 + return + } + + process(node.left) + process(node.right) + cur = (cur - node.val) / 10 + } + + process(root) + + return sum +} + +module.exports = sumNumbers diff --git a/problems/sum-root-to-leaf-numbers/sumNumbers.test.js b/problems/sum-root-to-leaf-numbers/sumNumbers.test.js new file mode 100644 index 0000000..3b7de97 --- /dev/null +++ b/problems/sum-root-to-leaf-numbers/sumNumbers.test.js @@ -0,0 +1,26 @@ +const { deserialize } = require('leettree') +const sumNumbers = require('./sumNumbers') + +test('Example 1', () => { + const root = deserialize([1, 2, 3]) + + const result = sumNumbers(root) + + expect(result).toBe(25) +}) + +test('Example 2', () => { + const root = deserialize([4, 9, 0, 5, 1]) + + const result = sumNumbers(root) + + expect(result).toBe(1026) +}) + +test('tree with null sibling node', () => { + const root = deserialize([1, 2, 3, null, 4, 5]) + + const result = sumNumbers(root) + + expect(result).toBe(259) +}) diff --git a/problems/surrounded-regions/README.md b/problems/surrounded-regions/README.md new file mode 100644 index 0000000..7138c69 --- /dev/null +++ b/problems/surrounded-regions/README.md @@ -0,0 +1,35 @@ +# Surrounded Regions + +LeetCode #: [130](https://leetcode.com/problems/surrounded-regions/) + +Difficulty: Medium + +Topic: Depth-first Search, Breadth-first Search, Union Find. + +## Problem + +Given a 2D board containing `'X'` and `'O'` (the letter O), capture all regions surrounded by `'X'`. + +A region is captured by flipping all `'O'`s into `'X'`s in that surrounded region. + +Example: + +```text +X X X X +X O O X +X X O X +X O X X +``` + +After running your function, the board should be: + +```text +X X X X +X X X X +X X X X +X O X X +``` + +Explanation: + +Surrounded regions shouldn’t be on the border, which means that any `'O'` on the border of the board are not flipped to `'X'`. Any `'O'` that is not on the border and it is not connected to an `'O'` on the border will be flipped to `'X'`. Two cells are connected if they are adjacent cells connected horizontally or vertically. diff --git a/problems/surrounded-regions/solve.js b/problems/surrounded-regions/solve.js new file mode 100644 index 0000000..77ce5af --- /dev/null +++ b/problems/surrounded-regions/solve.js @@ -0,0 +1,55 @@ +const markDfs = (board, row, col) => { + if (!board[row] || !board[row][col] || board[row][col] !== 'O') { + return + } + + board[row][col] = '!' + markDfs(board, row - 1, col) + markDfs(board, row, col + 1) + markDfs(board, row + 1, col) + markDfs(board, row, col - 1) +} + +/** + * @param {character[][]} board + * @return {void} Do not return anything, modify board in-place instead. + */ +const solve = function (board) { + if (!board.length) { + return + } + + for (let i = 0; i < board.length; i++) { + if (board[i][0] === 'O') { + markDfs(board, i, 0) + } + + const lastCol = board[i].length - 1 + if (board[i][lastCol] === 'O') { + markDfs(board, i, lastCol) + } + } + + for (let j = 1; j < board[0].length - 1; j++) { + if (board[0][j] === 'O') { + markDfs(board, 0, j) + } + + const lastRow = board.length - 1 + if (board[lastRow][j] === 'O') { + markDfs(board, lastRow, j) + } + } + + for (let i = 0; i < board.length; i++) { + for (let j = 0; j < board[i].length; j++) { + if (board[i][j] === 'O') { + board[i][j] = 'X' + } else if (board[i][j] === '!') { + board[i][j] = 'O' + } + } + } +} + +module.exports = solve diff --git a/problems/surrounded-regions/solve.test.js b/problems/surrounded-regions/solve.test.js new file mode 100644 index 0000000..6763ed5 --- /dev/null +++ b/problems/surrounded-regions/solve.test.js @@ -0,0 +1,45 @@ +const solve = require('./solve') + +test('Example 1', () => { + const board = [ + ['X', 'X', 'X', 'X'], + ['X', 'O', 'O', 'X'], + ['X', 'X', 'O', 'X'], + ['X', 'O', 'X', 'X'] + ] + + solve(board) + + expect(board).toStrictEqual([ + ['X', 'X', 'X', 'X'], + ['X', 'X', 'X', 'X'], + ['X', 'X', 'X', 'X'], + ['X', 'O', 'X', 'X'] + ]) +}) + +test('Empty board', () => { + const board = [] + + solve(board) + + expect(board).toStrictEqual([]) +}) + +test('corner and edge has 0', () => { + const board = [ + ['0', '0', 'X', 'O', 'X', 'X', 'O'], + ['X', 'X', 'X', 'O', 'X', 'X', 'X'], + ['O', 'X', 'O', 'X', 'X', 'O', 'X'], + ['O', 'X', 'O', 'X', 'X', 'O', 'O'] + ] + + solve(board) + + expect(board).toStrictEqual([ + ['0', '0', 'X', 'O', 'X', 'X', 'O'], + ['X', 'X', 'X', 'O', 'X', 'X', 'X'], + ['O', 'X', 'O', 'X', 'X', 'O', 'X'], + ['O', 'X', 'O', 'X', 'X', 'O', 'O'] + ]) +}) diff --git a/problems/two-city-scheduling/README.md b/problems/two-city-scheduling/README.md new file mode 100644 index 0000000..5394cf3 --- /dev/null +++ b/problems/two-city-scheduling/README.md @@ -0,0 +1,39 @@ +# Two City Scheduling + +LeetCode #: [1029](https://leetcode.com/problems/two-city-scheduling/) + +Difficulty: Easy + +Topic: Greedy. + +## Problem + +There are `2N` people a company is planning to interview. The cost of flying the `i`-th person to city `A` is `costs[i][0]`, and the cost of flying the `i`-th person to city `B` is `costs[i][1]`. + +Return the minimum cost to fly every person to a city such that exactly `N` people arrive in each city. + +Example 1: + +```text +Input: [[10,20],[30,200],[400,50],[30,20]] +Output: 110 +Explanation: +The first person goes to city A for a cost of 10. +The second person goes to city A for a cost of 30. +The third person goes to city B for a cost of 50. +The fourth person goes to city B for a cost of 20. + +The total minimum cost is 10 + 30 + 50 + 20 = 110 to have half the people interviewing in each city. +``` + +Note: + +- `1 <= costs.length <= 100` +- It is guaranteed that `costs.length` is even. +- `1 <= costs[i][0], costs[i][1] <= 1000` + +## Complexity Analysis + +Time complexity: O(n) + +Space complexity: O(n) diff --git a/problems/two-city-scheduling/twoCitySchedCost.js b/problems/two-city-scheduling/twoCitySchedCost.js new file mode 100644 index 0000000..c5501f5 --- /dev/null +++ b/problems/two-city-scheduling/twoCitySchedCost.js @@ -0,0 +1,23 @@ +/** + * @param {number[][]} costs + * @return {number} + */ +const twoCitySchedCost = function (costs) { + let result = 0 + const diffs = [] + for (let i = 0; i < costs.length; i++) { + const [a, b] = costs[i] + result += a + diffs.push(b - a) + } + + diffs.sort((a, b) => a - b) + + for (let i = 0; i < diffs.length / 2; i++) { + result += diffs[i] + } + + return result +} + +module.exports = twoCitySchedCost diff --git a/problems/two-city-scheduling/twoCitySchedCost.test.js b/problems/two-city-scheduling/twoCitySchedCost.test.js new file mode 100644 index 0000000..aae03cf --- /dev/null +++ b/problems/two-city-scheduling/twoCitySchedCost.test.js @@ -0,0 +1,9 @@ +const twoCitySchedCost = require('./twoCitySchedCost') + +test('Example 1', () => { + const costs = [[10, 20], [30, 200], [400, 50], [30, 20]] + + const result = twoCitySchedCost(costs) + + expect(result).toBe(110) +}) diff --git a/problems/unique-binary-search-trees/README.md b/problems/unique-binary-search-trees/README.md new file mode 100644 index 0000000..3fa12fe --- /dev/null +++ b/problems/unique-binary-search-trees/README.md @@ -0,0 +1,30 @@ +# Unique Binary Search Trees + +LeetCode #: [96](https://leetcode.com/problems/unique-binary-search-trees/) + +Difficulty: Medium. + +Topics: Dynamic Programming, Tree. + +## Problem + +Given n, how many structurally unique BST's (binary search trees) that store values 1 ... n? + +Example: + +```text +Input: 3 +Output: 5 +Explanation: +Given n = 3, there are a total of 5 unique BST's: + + 1 3 3 2 1 + \ / / / \ \ + 3 2 1 1 3 2 + / / \ \ + 2 1 2 3 +``` + +## Solution Explanation + +Reference: [DP Solution in 6 lines with explanation. F(i, n) = G(i-1) * G(n-i)](https://leetcode.com/problems/unique-binary-search-trees/discuss/31666/DP-Solution-in-6-lines-with-explanation.-F(i-n)-G(i-1)-*-G(n-i)) by [liaison](https://leetcode.com/liaison) diff --git a/problems/unique-binary-search-trees/numTrees.js b/problems/unique-binary-search-trees/numTrees.js new file mode 100644 index 0000000..66f62e0 --- /dev/null +++ b/problems/unique-binary-search-trees/numTrees.js @@ -0,0 +1,18 @@ +/** + * @param {number} n + * @return {number} + */ +const numTrees = function (n) { + const counts = new Array(n + 1).fill(0) + counts[0] = counts[1] = 1 + + for (let i = 2; i <= n; i++) { + for (let j = 1; j <= i; j++) { + counts[i] += counts[j - 1] * counts[i - j] + } + } + + return counts[n] +} + +module.exports = numTrees diff --git a/problems/unique-binary-search-trees/numTrees.test.js b/problems/unique-binary-search-trees/numTrees.test.js new file mode 100644 index 0000000..b1c5dc7 --- /dev/null +++ b/problems/unique-binary-search-trees/numTrees.test.js @@ -0,0 +1,9 @@ +const numTrees = require('./numTrees') + +test('Example 1', () => { + const n = 3 + + const result = numTrees(n) + + expect(result).toBe(5) +}) diff --git a/problems/unique-paths/README.md b/problems/unique-paths/README.md new file mode 100644 index 0000000..b1d57be --- /dev/null +++ b/problems/unique-paths/README.md @@ -0,0 +1,49 @@ +# Unique Paths + +LeetCode #: [62](https://leetcode.com/problems/unique-paths/) + +Difficulty: Medium + +Topics: Array, Dynamic Programming. + +## Problem + +A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). + +The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below). + +How many possible unique paths are there? + +![Problem](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) + +Above is a 7 x 3 grid. How many possible unique paths are there? + +Example 1: + +```text +Input: m = 3, n = 2 +Output: 3 +Explanation: +From the top-left corner, there are a total of 3 ways to reach the bottom-right corner: +1. Right -> Right -> Down +2. Right -> Down -> Right +3. Down -> Right -> Right +``` + +Example 2: + +```text +Input: m = 7, n = 3 +Output: 28 +``` + +Constraints: + +- `1 <= m, n <= 100` +- It's guaranteed that the answer will be less than or equal to `2 * 10 ^ 9`. + +## Complexity Analysis + +Time complexity: O(m * n). We loop through every cell in the m * n grid. + +Space complexiy: O(n). We use a `dp` array to store n numbers. diff --git a/problems/unique-paths/uniquePaths.js b/problems/unique-paths/uniquePaths.js new file mode 100644 index 0000000..4efc0fe --- /dev/null +++ b/problems/unique-paths/uniquePaths.js @@ -0,0 +1,18 @@ +/** + * @param {number} m + * @param {number} n + * @return {number} + */ +const uniquePaths = function (m, n) { + const dp = new Array(n).fill(1) + + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) { + dp[j] += dp[j - 1] + } + } + + return dp[n - 1] +} + +module.exports = uniquePaths diff --git a/problems/unique-paths/uniquePaths.test.js b/problems/unique-paths/uniquePaths.test.js new file mode 100644 index 0000000..0332d1e --- /dev/null +++ b/problems/unique-paths/uniquePaths.test.js @@ -0,0 +1,19 @@ +const uniquePaths = require('./uniquePaths') + +test('Example 1', () => { + const m = 3 + const n = 2 + + const result = uniquePaths(m, n) + + expect(result).toBe(3) +}) + +test('Example 2', () => { + const m = 7 + const n = 3 + + const result = uniquePaths(m, n) + + expect(result).toBe(28) +}) diff --git a/problems/validate-ip-address/README.md b/problems/validate-ip-address/README.md new file mode 100644 index 0000000..1e7ca9b --- /dev/null +++ b/problems/validate-ip-address/README.md @@ -0,0 +1,53 @@ +# Validate IP Address + +LeetCode #: [468](https://leetcode.com/problems/validate-ip-address/) + +Difficulty: Medium + +Topics: String. + +## Problem + +Write a function to check whether an input string is a valid IPv4 address or IPv6 address or neither. + +IPv4 addresses are canonically represented in dot-decimal notation, which consists of four decimal numbers, each ranging from 0 to 255, separated by dots ("."), e.g.,`172.16.254.1`; + +Besides, leading zeros in the IPv4 is invalid. For example, the address `172.16.254.01` is invalid. + +IPv6 addresses are represented as eight groups of four hexadecimal digits, each group representing 16 bits. The groups are separated by colons (":"). For example, the address `2001:0db8:85a3:0000:0000:8a2e:0370:7334` is a valid one. Also, we could omit some leading zeros among four hexadecimal digits and some low-case characters in the address to upper-case ones, so `2001:db8:85a3:0:0:8A2E:0370:7334` is also a valid IPv6 address(Omit leading zeros and using upper cases). + +However, we don't replace a consecutive group of zero value with a single empty group using two consecutive colons (::) to pursue simplicity. For example, `2001:0db8:85a3::8A2E:0370:7334` is an invalid IPv6 address. + +Besides, extra leading zeros in the IPv6 is also invalid. For example, the address `02001:0db8:85a3:0000:0000:8a2e:0370:7334` is invalid. + +Note: You may assume there is no extra space or special characters in the input string. + +Example 1: + +```text +Input: "172.16.254.1" + +Output: "IPv4" + +Explanation: This is a valid IPv4 address, return "IPv4". +``` + +Example 2: + +```text +Input: "2001:0db8:85a3:0:0:8A2E:0370:7334" + +Output: "IPv6" + +Explanation: This is a valid IPv6 address, return "IPv6". +``` + +Example 3: + +```text +Input: "256.256.256.256" + +Output: "Neither" + +Explanation: This is neither a IPv4 address nor a IPv6 address. +``` diff --git a/problems/validate-ip-address/validIPAddress.js b/problems/validate-ip-address/validIPAddress.js new file mode 100644 index 0000000..8a6290e --- /dev/null +++ b/problems/validate-ip-address/validIPAddress.js @@ -0,0 +1,59 @@ +const isIPv4 = (IP) => { + const groups = IP.split('.') + + if (groups.length !== 4) { + return false + } + + const nums = groups.map(g => parseInt(g)) + + for (let i = 0; i < nums.length; i++) { + if (nums[i] < 0 || nums[i] > 255) { + return false + } + + if (groups[i] !== nums[i].toString()) { + return false + } + } + + return true +} + +const isIPv6 = (IP) => { + const groups = IP.split(':') + + if (groups.length !== 8) { + return false + } + + const nums = groups.map(g => parseInt(g, 16)) + + for (let i = 0; i < nums.length; i++) { + if (nums[i] < 0 || nums[i] > 65535) { + return false + } + + const paddedGroup = groups[i].padStart(4, '0').toUpperCase() + const paddedNum = nums[i].toString(16).padStart(4, '0').toUpperCase() + if (paddedGroup !== paddedNum) { + return false + } + } + + return true +} + +/** + * @param {string} IP + * @return {string} + */ +const validIPAddress = function (IP) { + return ( + (isIPv4(IP) && 'IPv4') || + (isIPv6(IP) && 'IPv6') || + 'Neither' + ) +} + +module.exports = validIPAddress diff --git a/problems/validate-ip-address/validIPAddress.test.js b/problems/validate-ip-address/validIPAddress.test.js new file mode 100644 index 0000000..cc07898 --- /dev/null +++ b/problems/validate-ip-address/validIPAddress.test.js @@ -0,0 +1,105 @@ +const validIPAddress = require('./validIPAddress') + +test('Example 1', () => { + const IP = '172.16.254.1' + + const result = validIPAddress(IP) + + expect(result).toBe('IPv4') +}) + +test('Example 2', () => { + const IP = '2001:0db8:85a3:0:0:8A2E:0370:7334' + + const result = validIPAddress(IP) + + expect(result).toBe('IPv6') +}) + +test('Example 3', () => { + const IP = '256.256.256.256' + + const result = validIPAddress(IP) + + expect(result).toBe('Neither') +}) + +test('Valid IPv4', () => { + const IP = '172.16.254.1' + + const result = validIPAddress(IP) + + expect(result).toBe('IPv4') +}) + +test('Invalid IPv4 with leading zeros', () => { + const IP = '172.16.254.01' + + const result = validIPAddress(IP) + + expect(result).toBe('Neither') +}) + +test('Valid IPv6', () => { + const IP = '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + + const result = validIPAddress(IP) + + expect(result).toBe('IPv6') +}) + +test('Valid IPv6 with omitted leading zeros and low-case characters', () => { + const IP = '2001:db8:85a3:0:0:8A2E:0370:7334' + + const result = validIPAddress(IP) + + expect(result).toBe('IPv6') +}) + +test('Invalid IPv6 with two consecutive colons', () => { + const IP = '2001:0db8:85a3::8A2E:0370:7334' + + const result = validIPAddress(IP) + + expect(result).toBe('Neither') +}) + +test('Invalid IPv6 with extra leading zeros', () => { + const IP = '02001:0db8:85a3:0000:0000:8a2e:0370:7334' + + const result = validIPAddress(IP) + + expect(result).toBe('Neither') +}) + +test('IPv4 style that cannot be parsed to decimal should return Neither', () => { + const IP = '1e1.4.5.6' + + const result = validIPAddress(IP) + + expect(result).toBe('Neither') +}) + +test('IPv6 style that cannot be parsed from hexadecimal to decimal should return Neither', () => { + const IP = '20EE:FGb8:85a3:0:0:8A2E:0370:7334' + + const result = validIPAddress(IP) + + expect(result).toBe('Neither') +}) + +test('IPv6 style with "-" character should return Neither', () => { + const IP = '1081:db8:85a3:01:-0:8A2E:0370:7334' + + const result = validIPAddress(IP) + + expect(result).toBe('Neither') +}) + +test('IPv6 style with number larger than FFFF should return Neither', () => { + const IP = '1081:10000:db8:85a3:1:8A2E:0370:7334' + + const result = validIPAddress(IP) + + expect(result).toBe('Neither') +}) diff --git a/problems/word-search-ii/README.md b/problems/word-search-ii/README.md new file mode 100644 index 0000000..1e68ddb --- /dev/null +++ b/problems/word-search-ii/README.md @@ -0,0 +1,37 @@ +# Word Search II + +LeetCode #: [212](https://leetcode.com/problems/word-search-ii/) + +Difficulty: Hard + +Topics: Backtracking, Trie. + +## Problem + +Given a 2D board and a list of words from the dictionary, find all words in the board. + +Each word must be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once in a word. + +Example: + +```text +Input: +board = [ + ['o','a','a','n'], + ['e','t','a','e'], + ['i','h','k','r'], + ['i','f','l','v'] +] +words = ["oath","pea","eat","rain"] + +Output: ["eat","oath"] +``` + +Note: + +- All inputs are consist of lowercase letters `a-z`. +- The values of `words` are distinct. + +## Solution Explanation + +Reference: [Java 15ms Easiest Solution (100.00%)](https://leetcode.com/problems/word-search-ii/discuss/59780/Java-15ms-Easiest-Solution-(100.00)) by [yavinci](https://leetcode.com/yavinci/) diff --git a/problems/word-search-ii/findWords.js b/problems/word-search-ii/findWords.js new file mode 100644 index 0000000..4536a36 --- /dev/null +++ b/problems/word-search-ii/findWords.js @@ -0,0 +1,74 @@ +const charCodeA = 97 + +const getCharCodeIndex = (char) => { + return char.charCodeAt(0) - charCodeA +} + +const TrieNode = class { + constructor () { + this.next = new Array(26) + this.word = undefined + } +} + +const buildTrie = (words) => { + const root = new TrieNode() + + for (const word of words) { + let node = root + + for (const char of word) { + const idx = getCharCodeIndex(char) + node.next[idx] = node.next[idx] || new TrieNode() + node = node.next[idx] + } + + node.word = word + } + + return root +} + +const dfs = (board, i, j, node, result) => { + const char = board[i][j] + const idx = getCharCodeIndex(char) + if (char === '#' || node.next[idx] === undefined) { + return + } + + node = node.next[idx] + + if (node.word) { + result.push(node.word) + node.word = undefined + } + + board[i][j] = '#' + + if (i > 0) dfs(board, i - 1, j, node, result) + if (j > 0) dfs(board, i, j - 1, node, result) + if (i < board.length - 1) dfs(board, i + 1, j, node, result) + if (j < board[0].length - 1) dfs(board, i, j + 1, node, result) + + board[i][j] = char +} + +/** + * @param {character[][]} board + * @param {string[]} words + * @return {string[]} + */ +const findWords = function (board, words) { + const root = buildTrie(words) + const result = [] + + for (let i = 0; i < board.length; i++) { + for (let j = 0; j < board[i].length; j++) { + dfs(board, i, j, root, result) + } + } + + return result +} + +module.exports = findWords diff --git a/problems/word-search-ii/findWords.test.js b/problems/word-search-ii/findWords.test.js new file mode 100644 index 0000000..8c34f79 --- /dev/null +++ b/problems/word-search-ii/findWords.test.js @@ -0,0 +1,27 @@ +const findWords = require('./findWords') + +test('Example 1', () => { + const board = [ + ['o', 'a', 'a', 'n'], + ['e', 't', 'a', 'e'], + ['i', 'h', 'k', 'r'], + ['i', 'f', 'l', 'v'] + ] + const words = ['oath', 'pea', 'eat', 'rain'] + + const result = findWords(board, words) + + expect(result).toStrictEqual(['oath', 'eat']) +}) + +test('board contains every character from every words', () => { + const board = [ + ['a', 'b'], + ['d', 'c'] + ] + const words = ['abcd', 'dcba'] + + const result = findWords(board, words) + + expect(result).toStrictEqual(['abcd', 'dcba']) +})