diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index ae3fa11..28486a9 --- a/README.md +++ b/README.md @@ -1,161 +1,23 @@ # Overview -1. This is my Python (2.7) Leetcode solution. +1. This is my Python Leetcode solution. As time grows, this also become a guide to prepare for software engineer interview. -2. I really take time tried to make the best solution and collect the best resource that I found. -Because I wanted to help others like me. -If you like my answer, a star on GitHub means a lot to me. -https://github.com/wuduhren/leetcode-python +1. The solution is at `problems/python/` or `problems/python3/`. +For example, `merge-sorted-array.py`'s solution is at `https://leetcode.com/problems/python/merge-sorted-array/`. -3. The solution is at `problems/the-file-name/`. -For example, `merge-sorted-array.py`'s solution is at `https://leetcode.com/problems/merge-sorted-array/`. +2. I really take time tried to make the best solution and collect the best resource that I found. +Because I wanted to help others like me. +Please [BUY ME A COFFEE](https://www.buymeacoffee.com/chriswu) if you want to show support. -# Leetcode Similar Problems +# Leetcode Problem Lists I found it makes sense to solve similar problems together, so that we can recognize the problem faster when we encounter a new one. My suggestion is to skip the HARD problems when you first go through these list. -### Two Pointers -| Id | Name | Difficulty | Comments | -| ---: | --- | :---: | --- | -| 11 | [Container With Most Water](https://leetcode.com/problems/container-with-most-water/ "Container With Most Water") | ★★ | | -| 167 | [Two Sum II - Input array is sorted](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted "Two Sum II - Input array is sorted") | ★★ | | -| 977 | [Squares of a Sorted Array](https://leetcode.com/problems/squares-of-a-sorted-array "Squares of a Sorted Array") | ★★ | merge sort | - -### Recursion -| Id | Name | Difficulty | | | -| ---: | --- | :---: | :---: | --- | -| 726 | [Number of Atoms](https://leetcode.com/problems/number-of-atoms "Number of Atoms") | ★★★ | [736](https://leetcode.com/problems/parse-lisp-expression/ "736") | [394](https://leetcode.com/problems/decode-string/ "394") | -| 856 | [Score of Parentheses](https://leetcode.com/problems/score-of-parentheses/ "Score of Parentheses") | ★★★ | | | - -### Divide and Conquer -| Id | Name | Difficulty | Comments | -| ---: | --- | :---: | --- | -| 169 | [Majority Element](https://leetcode.com/problems/majority-element "Majority Element") | ★★ | | -| 315 | [Count of Smaller Numbers After Self](https://leetcode.com/problems/count-of-smaller-numbers-after-self/ "Count of Smaller Numbers After Self") | ★★★★ | merge sort / BIT | - -### Search -| Id | Name | Difficulty | | | | | | | Comments | -| ---: | --- | :---: | :---: | --- | --- | --- | --- | --- | --- | -| 17 | [Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number "Letter Combinations of a Phone Number") | ★★ | [39](https://leetcode.com/problems/combination-sum/ "39") | [40](https://leetcode.com/problems/combination-sum-ii/ "40") | [77](https://leetcode.com/problems/combinations/ "77") | [78](https://leetcode.com/problems/subsets/ "78") | [90](https://leetcode.com/problems/subsets-ii/ "90") | [216](https://leetcode.com/problems/combination-sum-iii/ "216") | Combination | -| 46 | [Permutations](https://leetcode.com/problems/permutations/ "Permutations") | ★★ | [47](https://leetcode.com/problems/permutations-ii/ "47") | [784](https://leetcode.com/problems/letter-case-permutation/ "784") | [943](https://leetcode.com/problems/find-the-shortest-superstring "943") | [996](https://leetcode.com/problems/number-of-squareful-arrays/ "996") | | | Permutation | -| 22 | [Generate Parentheses](https://leetcode.com/problems/generate-parentheses/ "Generate Parentheses") | ★★★ | [301](https://leetcode.com/problems/remove-invalid-parentheses/ "301") | | | | | | DFS | -| 37 | [Sudoku Solver](https://leetcode.com/problems/sudoku-solver "Sudoku Solver") | ★★★ | [51](https://leetcode.com/problems/n-queens "51") | [52](https://leetcode.com/problems/n-queens-ii "52") | | | | | DFS | -| 79 | [Word Search](https://leetcode.com/problems/word-search/ "Word Search") | ★★★ | [212](https://leetcode.com/problems/word-search-ii/ "212") | | | | | | DFS | -| 127 | [Word Ladder](https://leetcode.com/problems/word-ladder/ "Word Ladder") | ★★★★ | [126](https://leetcode.com/problems/word-ladder-ii/ "126") | [752](https://leetcode.com/problems/open-the-lock/ "752") | | | | | BFS | -| 542 | [01 Matrix](https://leetcode.com/problems/01-matrix/ "01 Matrix") | ★★★ | [675](https://leetcode.com/problems/cut-off-trees-for-golf-event/ "675") | [934](https://leetcode.com/problems/shortest-bridge/ "934") | | | | | BFS | -| 698 | [Partition to K Equal Sum Subsets](https://leetcode.com/problems/partition-to-k-equal-sum-subsets "Partition to K Equal Sum Subsets") | ★★★ | [93](https://leetcode.com/problems/restore-ip-addresses/ "93") | [131](https://leetcode.com/problems/palindrome-partitioning/ "131") | [241](https://leetcode.com/problems/different-ways-to-add-parentheses/ "241") | [282](https://leetcode.com/problems/expression-add-operators/ "282") | [842](https://leetcode.com/problems/split-array-into-fibonacci-sequence/ "842") | | Partition | - -### Hash Table -| Id | Name | Difficulty | | -| ---: | --- | :---: | :---: | -| 1 | [Two Sum](https://leetcode.com/problems/two-sum/ "Two Sum") | ★★ | [560](https://leetcode.com/problems/subarray-sum-equals-k/ "560") | - -### List -| Id | Name | Difficulty | | Comments | -| ---: | --- | :---: | :---: | --- | -| 2 | [Add Two Numbers](https://leetcode.com/problems/add-two-numbers/ "Add Two Numbers") | ★★ | [445](https://leetcode.com/problems/add-two-numbers-ii/ "445") | | -| 24 | [Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/ "Swap Nodes in Pairs") | ★★ | | | -| 206 | [Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/ "Reverse Linked List") | ★★ | | | -| 141 | [Linked List Cycle](https://leetcode.com/problems/linked-list-cycle/ "Linked List Cycle") | ★★ | [142](https://leetcode.com/problems/linked-list-cycle-ii "142") | fast/slow | -| 23 | [Merge k Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/ "Merge k Sorted Lists") | ★★★ | [21](https://leetcode.com/problems/merge-two-sorted-lists/ "21") | priority_queue | -| 147 | [Insertion Sort List](https://leetcode.com/problems/insertion-sort-list/ "Insertion Sort List") | ★★★ | | insertion sort | -| 148 | [Sort List](https://leetcode.com/problems/sort-list/ "Sort List") | ★★★★ | | merge sort O(1) space | -| 707 | [Design Linked List](https://leetcode.com/problems/design-linked-list "Design Linked List") | ★★★★ | | | - -### Tree -| Id | Name | Difficulty | | | | | | | Comments | -| ---: | --- | :---: | :---: | --- | --- | --- | --- | --- | --- | -| 94 | [Binary Tree Inorder Traversal](https://leetcode.com/problems/binary-tree-inorder-traversal/ "Binary Tree Inorder Traversal") | ★ | [589](https://leetcode.com/problems/n-ary-tree-preorder-traversal "589") | [590](https://leetcode.com/problems/n-ary-tree-postorder-traversal "590") | | | | | traversal | -| 100 | [Same Tree](https://leetcode.com/problems/same-tree/ "Same Tree") | ★★ | [101](https://leetcode.com/problems/symmetric-tree/ "101") | [104](https://leetcode.com/problems/maximum-depth-of-binary-tree/ "104") | [110](https://leetcode.com/problems/balanced-binary-tree/ "110") | [111](https://leetcode.com/problems/minimum-depth-of-binary-tree "111") | [572](https://leetcode.com/problems/subtree-of-another-tree "572") | [965](https://leetcode.com/problems/univalued-binary-tree/ "965") | | -| 102 | [Binary Tree Level Order Traversal](https://leetcode.com/problems/binary-tree-level-order-traversal/ "Binary Tree Level Order Traversal") | ★★ | [107](https://leetcode.com/problems/binary-tree-level-order-traversal-ii "107") | [429](https://leetcode.com/problems/n-ary-tree-level-order-traversal "429") | [872](https://leetcode.com/problems/leaf-similar-trees/ "872") | [987](https://leetcode.com/problems/vertical-order-traversal-of-a-binary-tree "987") | | | collecting nodes | -| 814 | [Binary Tree Pruning](https://leetcode.com/problems/binary-tree-pruning/ "Binary Tree Pruning") | ★★ | [669](https://leetcode.com/problems/trim-a-binary-search-tree/ "669") | | | | | | | -| 112 | [Path Sum](https://leetcode.com/problems/path-sum/ "Path Sum") | ★★★ | [113](https://leetcode.com/problems/path-sum-ii "113") | [437](https://leetcode.com/problems/path-sum-iii "437") | | | | | | -| 124 | [Binary Tree Maximum Path Sum](https://leetcode.com/problems/binary-tree-maximum-path-sum/ "Binary Tree Maximum Path Sum") | ★★★ | [543](https://leetcode.com/problems/diameter-of-binary-tree/ "543") | [687](https://leetcode.com/problems/longest-univalue-path/ "687") | | | | | Use both children, return one | -| 129 | [Sum Root to Leaf Numbers](https://leetcode.com/problems/sum-root-to-leaf-numbers/ "Sum Root to Leaf Numbers") | ★★★ | [257](https://leetcode.com/problems/binary-tree-paths/ "257") | | | | | | | -| 236 | [Lowest Common Ancestor of a Binary Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/ "Lowest Common Ancestor of a Binary Tree") | ★★★ | [235](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree "235") | | | | | | | -| 297 | [Serialize and Deserialize Binary Tree](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/ "Serialize and Deserialize Binary Tree") | ★★★ | [449](https://leetcode.com/problems/serialize-and-deserialize-bst "449") | | | | | | | -| 508 | [Most Frequent Subtree Sum](https://leetcode.com/problems/most-frequent-subtree-sum/ "Most Frequent Subtree Sum") | ★★★ | | | | | | | | -| 968 | [Binary Tree Cameras](https://leetcode.com/problems/binary-tree-cameras/ "Binary Tree Cameras") | ★★★★ | [337](https://leetcode.com/problems/house-robber-iii/ "337") | [979](https://leetcode.com/problems/distribute-coins-in-binary-tree "979") | | | | | | - -### Binary Search -| Id | Name | Difficulty | | | | | | Comments | -| ---: | --- | :---: | :---: | --- | --- | --- | --- | --- | -| 35 | [Search Insert Position](https://leetcode.com/problems/search-insert-position/ "Search Insert Position") | ★★ | [34](https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/ "34") | [704](https://leetcode.com/problems/binary-search/ "704") | [981](https://leetcode.com/problems/time-based-key-value-store "981") | | | upper_bound | -| 33 | [Search in Rotated Sorted Array](https://leetcode.com/problems/search-in-rotated-sorted-array "Search in Rotated Sorted Array") | ★★★ | [81](https://leetcode.com/problems/search-in-rotated-sorted-array-ii/ "81") | [153](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/ "153") | [154](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array-ii "154") | [162](https://leetcode.com/problems/find-peak-element "162") | [852](https://leetcode.com/problems/peak-index-in-a-mountain-array/ "852") | rotated / peak | -| 69 | [Sqrt(x)](https://leetcode.com/problems/sqrtx "Sqrt(x)") | ★★★ | | | | | | upper_bound | -| 74 | [Search a 2D Matrix](https://leetcode.com/problems/search-a-2d-matrix/ "Search a 2D Matrix") | ★★★ | | | | | | treat 2d as 1d | -| 875 | [Koko Eating Bananas](https://leetcode.com/problems/koko-eating-bananas/ "Koko Eating Bananas") | ★★★ | [1011](https://leetcode.com/problems/capacity-to-ship-packages-within-d-days/ "1011") | | | | | guess ans and check | -| 378 | [Kth Smallest Element in a Sorted Matrix](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/ "Kth Smallest Element in a Sorted Matrix") | ★★★ | [668](https://leetcode.com/problems/kth-smallest-number-in-multiplication-table/ "668") | | | | | kth + matrix | -| 778 | [Swim in Rising Water](https://leetcode.com/problems/swim-in-rising-water/ "Swim in Rising Water") | ★★★ | [174](https://leetcode.com/problems/dungeon-game/ "174") | [875](https://leetcode.com/problems/koko-eating-bananas/ "875") | | | | guess ans and check | -| 4 | [Median of Two Sorted Arrays](https://leetcode.com/problems/median-of-two-sorted-arrays/ "Median of Two Sorted Arrays") | ★★★★ | | | | | | | -| 719 | [Find K-th Smallest Pair Distance](https://leetcode.com/problems/find-k-th-smallest-pair-distance/ "Find K-th Smallest Pair Distance") | ★★★★ | [786](https://leetcode.com/problems/k-th-smallest-prime-fraction/ "786") | | | | | kth + two pointers | - -### Binary Search Tree -| Id | Name | Difficulty | | Comments | -| ---: | --- | :---: | :---: | --- | -| 98 | [Validate Binary Search Tree](https://leetcode.com/problems/validate-binary-search-tree/ "Validate Binary Search Tree") | ★★ | [530](https://leetcode.com/problems/minimum-absolute-difference-in-bst "530") | inorder | -| 700 | [Search in a Binary Search Tree](https://leetcode.com/problems/search-in-a-binary-search-tree/ "Search in a Binary Search Tree") | ★★ | [701](https://leetcode.com/problems/insert-into-a-binary-search-tree/ "701") | binary search | -| 230 | [Kth Smallest Element in a BST](https://leetcode.com/problems/kth-smallest-element-in-a-bst "Kth Smallest Element in a BST") | ★★★ | | inorder | -| 99 | [Recover Binary Search Tree](https://leetcode.com/problems/recover-binary-search-tree/ "Recover Binary Search Tree") | ★★★ | | inorder | -| 108 | [Convert Sorted Array to Binary Search Tree](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/ "Convert Sorted Array to Binary Search Tree") | ★★★ | | | -| 501 | [Find Mode in Binary Search Tree](https://leetcode.com/problems/find-mode-in-binary-search-tree/ "Find Mode in Binary Search Tree") | ★★★ | | inorder | -| 450 | [Delete Node in a BST](https://leetcode.com/problems/delete-node-in-a-bst/ "Delete Node in a BST") | ★★★★ | | binary search | - -### Graph -| Id | Name | Difficulty | | | | | Comments | -| ---: | --- | :---: | :---: | --- | --- | --- | --- | -| 133 | [Clone Graph](https://leetcode.com/problems/clone-graph/ "Clone Graph") | ★★ | [138](https://leetcode.com/problems/copy-list-with-random-pointer/ "138") | | | | queue + hashtable | -| 200 | [Number of Islands](https://leetcode.com/problems/number-of-islands/ "Number of Islands") | ★★ | [547](https://leetcode.com/problems/friend-circles/ "547") | [695](https://leetcode.com/problems/max-area-of-island "695") | [733](https://leetcode.com/problems/flood-fill/ "733") | [827](https://leetcode.com/problems/making-a-large-island/ "827") | grid + connected components | -| 841 | [Keys and Rooms](https://leetcode.com/problems/keys-and-rooms/ "Keys and Rooms") | ★★ | | | | | connected components | -| 207 | [Course Schedule](https://leetcode.com/problems/course-schedule/ "Course Schedule") | ★★★ | [210](https://leetcode.com/problems/course-schedule-ii/ "210") | [802](https://leetcode.com/problems/find-eventual-safe-states "802") | | | topology sorting | -| 399 | [Evaluate Division](https://leetcode.com/problems/evaluate-division "Evaluate Division") | ★★★ | [839](https://leetcode.com/problems/similar-string-groups "839") | [952](https://leetcode.com/problems/largest-component-size-by-common-factor/ "952") | [990](https://leetcode.com/problems/satisfiability-of-equality-equations "990") | [721](https://leetcode.com/problems/accounts-merge/ "721") | union find | -| 785 | [Is Graph Bipartite?](https://leetcode.com/problems/is-graph-bipartite "Is Graph Bipartite?") | ★★★ | | | | | bipartition | -| 684 | [Redundant Connection](https://leetcode.com/problems/redundant-connection "Redundant Connection") | ★★★★ | [685](https://leetcode.com/problems/redundant-connection-ii "685") | [787](https://leetcode.com/problems/cheapest-flights-within-k-stops/ "787") | | | cycle, union find | -| 743 | [Network Delay Time](https://leetcode.com/problems/network-delay-time "Network Delay Time") | ★★★★ | [882](https://leetcode.com/problems/reachable-nodes-in-subdivided-graph/ "882") | | | | shortest path | -| 847 | [Shortest Path Visiting All Nodes](https://leetcode.com/problems/shortest-path-visiting-all-nodes/ "Shortest Path Visiting All Nodes") | ★★★★ | [815](https://leetcode.com/problems/bus-routes/ "815") | [864](https://leetcode.com/problems/shortest-path-to-get-all-keys/ "864") | [924](https://leetcode.com/problems/minimize-malware-spread/ "924") | | BFS | -| 943 | [Find the Shortest Superstring](https://leetcode.com/problems/find-the-shortest-superstring/ "Find the Shortest Superstring") | ★★★★ | [980](https://leetcode.com/problems/unique-paths-iii/ "980") | [996](https://leetcode.com/problems/number-of-squareful-arrays/ "996") | | | Hamiltonian path (DFS / DP) | -| 959 | [Regions Cut By Slashes](https://leetcode.com/problems/regions-cut-by-slashes/ "Regions Cut By Slashes") | ★★★★ | | | | | union find / grid + connected component | -| 332 | [Reconstruct Itinerary](https://leetcode.com/problems/reconstruct-itinerary/ "Reconstruct Itinerary") | ★★★★ | | | | | Eulerian path | -| 1192 | [Critical Connections in a Network](https://leetcode.com/problems/critical-connections-in-a-network/ "Critical Connections in a Network") | ★★★★ | | | | | Tarjan | - - -### Dynamic Programming -| Id | Name | Difficulty | | | | | | | Comments | -| ---: | --- | :---: | :---: | --- | --- | --- | --- | --- | --- | -| 70 | [Climbing Stairs](https://leetcode.com/problems/climbing-stairs "Climbing Stairs") | ★ | [746](https://leetcode.com/problems/min-cost-climbing-stairs "746") | | | | | | I: O(n), S = O(n), T = O(n) | -| 303 | [Range Sum Query - Immutable](https://leetcode.com/problems/range-sum-query-immutable "Range Sum Query - Immutable") | ★ | | | | | | | | -| 53 | [Maximum Subarray](https://leetcode.com/problems/maximum-subarray "Maximum Subarray") | ★★ | [121](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/ "121") | | | | | | | -| 198 | [House Robber](https://leetcode.com/problems/house-robber/ "House Robber") | ★★★ | [213](https://leetcode.com/problems/house-robber-ii/ "213") | [309](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/ "309") | [740](https://leetcode.com/problems/delete-and-earn/ "740") | [790](https://leetcode.com/problems/domino-and-tromino-tiling/ "790") | [801](https://leetcode.com/problems/minimum-swaps-to-make-sequences-increasing/ "801") | | I: O(n), S = O(3n), T = O(3n) | -| 139 | [Word Break](https://leetcode.com/problems/word-break "Word Break") | ★★★ | [140](https://leetcode.com/problems/word-break-ii "140") | [818](https://leetcode.com/problems/race-car/ "818") | | | | | I: O(n), S = O(n), T = O(n^2) | -| 300 | [Longest Increasing Subsequence](https://leetcode.com/problems/longest-increasing-subsequence/ "Longest Increasing Subsequence") | ★★★ | [673](https://leetcode.com/problems/number-of-longest-increasing-subsequence "673") | | | | | | | -| 72 | [Edit Distance](https://leetcode.com/problems/edit-distance "Edit Distance") | ★★★ | [10](https://leetcode.com/problems/regular-expression-matching "10") | [44](https://leetcode.com/problems/wildcard-matching/ "44") | [97](https://leetcode.com/problems/interleaving-string "97") | [115](https://leetcode.com/problems/distinct-subsequences/ "115") | [583](https://leetcode.com/problems/delete-operation-for-two-strings/ "583") | [712](https://leetcode.com/problems/minimum-ascii-delete-sum-for-two-strings "712") | I: O(m+n), S = O(mn), T = O(mn) | -| 322 | [Coin Change](https://leetcode.com/problems/coin-change "Coin Change") | ★★★ | [377](https://leetcode.com/problems/combination-sum-iv/ "377") | [416](https://leetcode.com/problems/partition-equal-subset-sum/ "416") | [494](https://leetcode.com/problems/target-sum "494") | | | | I: O(n) + k, S = O(n), T = O(kn) | -| 813 | [Largest Sum of Averages](https://leetcode.com/problems/largest-sum-of-averages/ "Largest Sum of Averages") | ★★★ | | | | | | | I: O(n) + k, S = O(n), T = O(kn^2) | -| 312 | [Burst Balloons](https://leetcode.com/problems/burst-balloons/ "Burst Balloons") | ★★★★ | [664](https://leetcode.com/problems/strange-printer/ "664") | [1024](https://leetcode.com/problems/video-stitching/ "1024") | [1039](https://leetcode.com/problems/minimum-score-triangulation-of-polygon/ "1039") | | | | I: O(n), S = O(n^2), T = O(n^3) | -| 741 | [Cherry Pickup](https://leetcode.com/problems/cherry-pickup/ "Cherry Pickup") | ★★★★ | | | | | | | I: O(n^2), S = O(n^3), T = O(n^3) | -| 546 | [Remove Boxes](https://leetcode.com/problems/remove-boxes "Remove Boxes") | ★★★★★ | | | | | | | I: O(n), S = O(n^3), T = O(n^4) | -| 943 | [Find the Shortest Superstring](https://leetcode.com/problems/find-the-shortest-superstring/ "Find the Shortest Superstring") | ★★★★ | [980](https://leetcode.com/problems/unique-paths-iii/ "980") | [996](https://leetcode.com/problems/number-of-squareful-arrays/ "996") | | | | | I: O(n), S = O(n*2^n), T = (n^2*2^n) | -| 62 | [Unique Paths](https://leetcode.com/problems/unique-paths "Unique Paths") | ★★ | [63](https://leetcode.com/problems/unique-paths-ii "63") | [64](https://leetcode.com/problems/minimum-path-sum "64") | [120](https://leetcode.com/problems/triangle "120") | [174](https://leetcode.com/problems/dungeon-game "174") | [931](https://leetcode.com/problems/minimum-falling-path-sum/ "931") | | I: O(mn), S = O(mn), T = O(mn) | -| 85 | [Maximal Rectangle](https://leetcode.com/problems/delete-operation-for-two-strings/ "Maximal Rectangle") | ★★★ | [221](https://leetcode.com/problems/maximal-square/ "221") | [304](https://leetcode.com/problems/range-sum-query-2d-immutable "304") | | | | | | -| 688 | [Knight Probability in Chessboard](https://leetcode.com/problems/knight-probability-in-chessboard/ "Knight Probability in Chessboard") | ★★★ | [576](https://leetcode.com/problems/out-of-boundary-paths/ "576") | [935](https://leetcode.com/problems/knight-dialer/ "935") | | | | | I: O(mn) + k, S = O(kmn) T = O(kmn) | -| 322 | [Coin Change](https://leetcode.com/problems/reconstruct-itinerary/ "Coin Change") | ★★★ | [377](https://leetcode.com/problems/combination-sum-iv/ "377") | [416](https://leetcode.com/problems/partition-equal-subset-sum/ "416") | [494](https://leetcode.com/problems/target-sum/ "494") | [1043](https://leetcode.com/problems/partition-array-for-maximum-sum/ "1043") | [1049](https://leetcode.com/problems/last-stone-weight-ii/ "1049") | | I: O(n) + k, S = O(n), T = O(kn) | -| | | | [1220](https://leetcode.com/problems/count-vowels-permutation/ "1220") | [1230](https://leetcode.com/problems/toss-strange-coins/ "1230") | [1262](https://leetcode.com/problems/greatest-sum-divisible-by-three/ "1262") | [1269](https://leetcode.com/problems/number-of-ways-to-stay-in-the-same-place-after-some-steps/ "1269") | | | -| 813 | [Largest Sum of Averages](https://leetcode.com/problems/largest-sum-of-averages/ "Largest Sum of Averages") | ★★★★ | [1278](https://leetcode.com/problems/palindrome-partitioning-iii/ "1278") | [1335](https://leetcode.com/problems/minimum-difficulty-of-a-job-schedule/ "1335") | [410](https://leetcode.com/problems/split-array-largest-sum/ "410") | | | | I: O(n) + k
S = O(n*k), T = O(kn^2) | -| 1223 | [Dice Roll Simulation](https://leetcode.com/problems/dice-roll-simulation/ "Dice Roll Simulation") | ★★★★ | | | | | | | I: O(n) + k + p
S = O(k*p), T = O(n^2kp) | -| 312 | [Burst Balloons](https://leetcode.com/problems/burst-balloons/ "Burst Balloons") | ★★★★ | [664](https://leetcode.com/problems/strange-printer/ "664") | [1024](https://leetcode.com/problems/video-stitching/ "1024") | [1039](https://leetcode.com/problems/minimum-score-triangulation-of-polygon/ "1039") | [1140](https://leetcode.com/problems/stone-game-ii/ "1140") | [1130](https://leetcode.com/problems/minimum-cost-tree-from-leaf-values/ "1130") | | I: O(n), S = O(n^2), T = O(n^3) | -| 741 | [Cherry Pickup](https://leetcode.com/problems/cherry-pickup/ "Cherry Pickup") | ★★★★ | | | | | | | I: O(n^2), S = O(n^3), T = O(n^3) | -| 546 | [Remove Boxes](https://leetcode.com/problems/remove-boxes/ "Remove Boxes") | ★★★★★ | | | | | | | I: O(n), S = O(n^3), T = O(n^4) | -| 943 | [Find the Shortest Superstring](https://leetcode.com/problems/find-the-shortest-superstring/ "Find the Shortest Superstring") | ★★★★★ | [980](https://leetcode.com/problems/unique-paths-iii/ "980") | [996](https://leetcode.com/problems/number-of-squareful-arrays/ "996") | [1125](https://leetcode.com/problems/smallest-sufficient-team/ "1125") | | | | I: O(n)
S = O(n*2^n), T = (n^2*2^n) | - -### Advanced -| Id | Name | Difficulty | | | | | | Comments | -| ---: | --- | :---: | :---: | --- | --- | --- | --- | --- | -| 208 | [Implement Trie (Prefix Tree)](https://leetcode.com/problems/implement-trie-prefix-tree "Implement Trie (Prefix Tree)") | ★★★ | [648](https://leetcode.com/problems/replace-words/ "648") | [676](https://leetcode.com/problems/implement-magic-dictionary "676") | [677](https://leetcode.com/problems/map-sum-pairs "677") | [720](https://leetcode.com/problems/longest-word-in-dictionary "720") | [745](https://leetcode.com/problems/prefix-and-suffix-search "745") | Trie | -| 307 | [Range Sum Query - Mutable](https://leetcode.com/problems/range-sum-query-mutable "Range Sum Query - Mutable") | ★★★ | | | | | | BIT/Segment Tree | -| 901 | [Online Stock Span](https://leetcode.com/problems/online-stock-span "Online Stock Span") | ★★★ | [907](https://leetcode.com/problems/sum-of-subarray-minimums "907") | [1019](https://leetcode.com/problems/next-greater-node-in-linked-list/ "1019") | | | | Monotonic Stack | -| 239 | [Sliding Window Maximum](https://leetcode.com/problems/sliding-window-maximum/ "Sliding Window Maximum") | ★★★ | | | | | | Monotonic Queue | - -This [list](https://docs.google.com/spreadsheets/d/1SbpY-04Cz8EWw3A_LBUmDEXKUMO31DBjfeMoA0dlfIA/edit#gid=126913158) is made by **huahua**, I found this on his [youtube](https://www.youtube.com/user/xxfflower/videos). Please visit his [website](https://zxi.mytechroad.com/blog/leetcode-problem-categories/) for more. +* https://neetcode.io/practice (150 problems with video explaination) +* https://www.programcreek.com/2013/08/leetcode-problem-classification/ +* https://github.com/wisdompeak/LeetCode +* https://docs.google.com/spreadsheets/d/1SbpY-04Cz8EWw3A_LBUmDEXKUMO31DBjfeMoA0dlfIA/edit#gid=126913158 ([huahua](https://www.youtube.com/user/xxfflower/videos)). +* https://leetcode.com/list/?selectedList=535ukjh5 (Only 74 problems) # Software Engineer Interview @@ -176,7 +38,7 @@ This [list](https://docs.google.com/spreadsheets/d/1SbpY-04Cz8EWw3A_LBUmDEXKUMO3 3. [What should I know from the CLRS 3rd edition book if my aim is to get into Google?](https://www.quora.com/What-should-I-know-from-the-CLRS-3rd-edition-book-if-my-aim-is-to-get-into-Google/answer/Jimmy-Saade) ## Data Structures and Algorithms for beginners -If you are new or know nothing about data structures and algorithms, I recommend [this course](). This course is taught in Python and design to help you find job and do well in the interview. +If you are new or know nothing about data structures and algorithms, I recommend [this course](). This course is taught in Python and design to help you find job and do well in the interview. # System Design @@ -188,6 +50,8 @@ If you are new or know nothing about data structures and algorithms, I recommend 4. [Narendra's Youtube Channel](https://www.youtube.com/channel/UCn1XnDWhsLS5URXTi5wtFTA/playlists) +5. [System Design Primer](https://github.com/donnemartin/system-design-primer) + # Knowledge Base Question 1. [Session vs Cookie](https://medium.com/@chriswrite/session-vs-cookie-software-engineer-top-asked-question-1-9bdbc0766739) diff --git "a/common/bellman\342\200\223ford.py" "b/common/bellman\342\200\223ford.py" new file mode 100755 index 0000000..e5521b4 --- /dev/null +++ "b/common/bellman\342\200\223ford.py" @@ -0,0 +1,31 @@ +""" +First, we use `distance` to track the distance from start to all others. +For V nodes, it takes at least V-1 iteration to complete the algorithm. +For every iteration, +We use every node as the middle point to see if it can "loosen" the path from start to mid to mid's neighbors +If it can loosen the path, we update the `distance`. +The time complexity is O(VE) +""" +def min_path(G, N, start, end): + distance = [float('inf') for _ in xrange(N+1)] + distance[start] = 0 + + for _ in xrange(N-1): + for mid, dis in enumerate(distance): + if dis==float('inf'): continue + for dis_to_nei, nei in G[mid]: + distance[nei] = min(distance[nei], dis+dis_to_nei) + return distance[end] + + +G = { + 0: [(-2, 1), (4, 2)], + 1: [(5, 2)], + 2: [(12, 3), (5, 4)], + 3: [(-8, 4)], + 4: [] +} +N = 4 #nodes count +start = 0 +end = 4 +print min_path(G, N, start, end) diff --git a/common/binary-search-tree.py b/common/binary-search-tree.py new file mode 100755 index 0000000..667b0a9 --- /dev/null +++ b/common/binary-search-tree.py @@ -0,0 +1,97 @@ +class Node(object): + def __init__(self, val): + self.left = None + self.right = None + self.val = val + + +class BinarySearchTree(object): + def __init__(self, val): + self.root = Node(val) + + def insert(self, val): + def helper(node, val): + if val>node.val: + if node.right: + helper(node.right, val) + else: + node.right = Node(val) + elif valnode.val: + return helper(node.right, val) + elif valdis_to_mid+dis: + distance[nei] = dis_to_mid+dis + prev[nei] = mid + if nei not in visited: + heapq.heappush(pq, (distance[nei], nei)) + + curr = end + while True: + if curr not in prev: break + path_str = ' -> '+prev[curr]+path_str + curr = prev[curr] + + return path_str+' = '+str(distance[end]) + + + +#normal implementation +def min_path(G, start, end): + distance = {} #shortest distance from start + prev = {} + path_str = '' + visited = set() + + distance[start] = 0 + while True: + #find nearest unvisited node + mid = None + dis_to_mid = float('inf') + for node in distance: + if node not in visited and distance[node]A[largest]: largest = l + if rA[largest]: largest = r + if largest!=i: + A[largest], A[i] = A[i], A[largest] + heapify(A, N, largest) + +#O(NLogN) +def heapSort(A): + N = len(A) + + #build max heap, O(NLogN). Can be optimized to the O(N). + for i in range(N//2-1, -1, -1): + heapify(A, N, i) + + #keep swapping the largest + for i in range(N-1, -1, -1): + A[0], A[i] = A[i], A[0] + heapify(A, i, 0) + + +A = [12, 11, 13, 5, 6, 7] +heapSort(A) +print(A) + +A = [1, 3, 5, 4, 6, 13, 10, 9, 8, 15, 17] +heapSort(A) +print(A) \ No newline at end of file diff --git a/common/insertion-sort.py b/common/insertion-sort.py new file mode 100755 index 0000000..c5961f9 --- /dev/null +++ b/common/insertion-sort.py @@ -0,0 +1,19 @@ +def insertionSort(nums): + for i in xrange(len(nums)): + num = nums[i] + j = i-1 + while j>=0 and nummw: + K[i][mw] = K[i-1][mw] + else: + K[i][mw] = max(v+K[i-1][mw-w], K[i-1][mw]) + + max_value = K[-1][-1] + + w = max_weight + i = N + while i>0: + if K[i][w]-K[i-1][w]>0: + bag.append(i-1) + w = w - weight[i-1] + i = i-1 + + print 'bag: ', bag + print 'max_value', max_value + return max_value + + +value = [60, 100, 120] +weight = [10, 20, 30] +max_weight = 50 +get_max_value(value, weight, max_weight) + +value = [40, 100, 50, 60] +weight = [20, 10, 40, 30] +max_weight = 60 +get_max_value(value, weight, max_weight) + diff --git a/common/merge-sort-in-place.py b/common/merge-sort-in-place.py new file mode 100755 index 0000000..87916b2 --- /dev/null +++ b/common/merge-sort-in-place.py @@ -0,0 +1,22 @@ +def merge(A, start, mid, end): + L, R = A[start:mid], A[mid:end] + i = j = 0 + k = start + for l in xrange(k,end): + if j>=len(R) or (i 1: + mid = (p+r)/2 + mergeSort(A, p, mid) + mergeSort(A, mid, r) + merge(A, p, mid, r) + +A = [20, 30, 21, 15, 42, 45, 31, 0, 9] +mergeSort(A, 0, len(A)) +print A diff --git a/common/merge-sort.py b/common/merge-sort.py new file mode 100755 index 0000000..fae511f --- /dev/null +++ b/common/merge-sort.py @@ -0,0 +1,58 @@ +def mergeSort(A): + def merge(a1, a2): + opt = [] + i1 = i2 = 0 + while i1self.heap[i]: + self.heap[p], self.heap[i] = self.heap[i], self.heap[p] + bubbleUp(p) + + self.heap.append(x) + bubbleUp(self.size) + self.size += 1 + + def pop(): + if self.size<=0: return None + m = self.heap[0] + remove(0) + return m + + def remove(self, i): + def bubbleDown(i): + l = i*2+1 + r = i*2+2 + left_child = self.heap[l] if lleft_child or self.heap[i]>right_child: + if left_child=self.size: + return + elif i==self.size-1: + self.heap = self.heap[:-1] + self.size -= 1 + return + else: + self.heap[i], self.heap[-1] = self.heap[-1], self.heap[i] + self.heap = self.heap[:-1] + self.size -= 1 + bubbleDown(i) + return + + + + + + +min_heap = MinHeap() +min_heap.push(3) +min_heap.push(1) +min_heap.push(2) +min_heap.push(10) +min_heap.push(4) +min_heap.push(7) +min_heap.push(9) + +print min_heap.heap + +min_heap.remove(2) +print min_heap.heap + +min_heap.remove(2) +min_heap.remove(2) +min_heap.remove(2) +min_heap.remove(2) +print min_heap.heap + +min_heap.remove(2) +min_heap.remove(1) +min_heap.remove(0) +print min_heap.heap + + \ No newline at end of file diff --git a/common/quick-sort.py b/common/quick-sort.py new file mode 100755 index 0000000..e4f1fdc --- /dev/null +++ b/common/quick-sort.py @@ -0,0 +1,32 @@ +def quickSort(A): + def sortRange(A, l, r): + if l>=r: return A + + p = A[(l+r)/2] + i = partition(A, l, r, p) + sortRange(A, l, i-1) + sortRange(A, i, r) + return A + + def partition(A, l, r, p): + while l<=r: + while A[l]p: r -= 1 + if l<=r: + A[l], A[r] = A[r], A[l] + l += 1 + r -= 1 + return l + + return sortRange(A, 0, len(A)-1) + + +A = [5,2,4,1,3,6,0] +print quickSort(A) + + +""" +Time Complexity is O(NlogN) on best and average case. +O(N^2) on the worst case, because if you choose the smallest pivot everytime the array will decay in a linear time. +Space Complexity O(LogN), for we need to store the stack for the recursion. +""" \ No newline at end of file diff --git a/common/radix-sort.py b/common/radix-sort.py new file mode 100755 index 0000000..b9bb006 --- /dev/null +++ b/common/radix-sort.py @@ -0,0 +1,21 @@ +def radixSort(nums): + maxNumberOfDigits = len(str(max(nums))) + + for d in xrange(maxNumberOfDigits): + b = [[] for _ in xrange(10)] + + for num in nums: + n = (num//10**d)%10 + print num, d, n + b[n].append(num) + + i = 0 + for a in b: + for num in a: + nums[i] = num + i += 1 + + +test = [21, 4, 1, 3, 9, 20, 25, 6, 21, 14] +radixSort(test) +print test \ No newline at end of file diff --git a/common/trie.py b/common/trie.py new file mode 100755 index 0000000..d2bf79d --- /dev/null +++ b/common/trie.py @@ -0,0 +1,87 @@ +class Node(object): + def __init__(self): + self.children = {} + self.isEnd = False + + def get(self, char): + if char in self.children: + return self.children[char] + else: + return None + + def set(self, char): + if char in self.children: + return self.children[char] + else: + self.children[char] = Node() + return self.children[char] + + def remove(self, char): + self.children.pop(char, None) + return len(self.children)==0 + + +class Trie(object): + def __init__(self): + self.root = Node() + + def insert(self, word): + curr = self.root + for i in xrange(len(word)): + c = word[i] + node = curr.get(c) + if node is None: node = curr.set(c) + if i==len(word)-1: node.isEnd = True + curr = node + + def search(self, word): + curr = self.root + for i in xrange(len(word)): + c = word[i] + node = curr.get(c) + if node is None: return False + if i==len(word)-1: return node.isEnd + curr = node + + def remove(self, word): + curr = self.root + stack = [] + for i in xrange(len(word)): + c = word[i] + node = curr.get(c) + + #the word does not exist + if node is None: return + + stack.append((curr, c)) + curr = node + + #if the last node has other link + #just set isEnd to False + if len(curr.children)>0: + curr.isEnd = False + return + + while stack and len(stack)>0: + node, c = stack.pop() + emptyAfterRemove = node.remove(c) + if not emptyAfterRemove: break + + +trie = Trie() +trie.insert('abc') +trie.insert('abgl') +trie.insert('cdf') +trie.insert('abcd') +trie.insert('lmn') +print trie.search('abc') +print trie.search('abcd') + +trie.remove('abc') +print trie.search('abc') +trie.remove('abgl') +print trie.search('abgl') +trie.remove('abcd') +print trie.search('abcd') + + diff --git a/problems/backspace-string-compare.py b/problems/backspace-string-compare.py deleted file mode 100644 index 9c9c8f5..0000000 --- a/problems/backspace-string-compare.py +++ /dev/null @@ -1,41 +0,0 @@ -#https://leetcode.com/problems/backspace-string-compare/ -class Solution: - #Compare from backward. Because we need to count the hashtags first. - #Compare the char that are not canceled by the hashtags, letter by letter. - #So we don't have to convert the whole string if they are not the same. - #Return Fasle, as soon as we find out that they are not the same. - - def backspaceCompare(self, S1, S2): - #index compared so far - i = len(S1)-1 - j = len(S2)-1 - - while i>=0 or j>=0: - #the first char that are not canceled by the hashtags - c1 = '' - c2 = '' - - if i>=0: - c1, i = self.getChar(S1, i) - if j>=0: - c2, j = self.getChar(S2, j) - if c1!=c2: - return False - return True - - def getChar(self, s, i): - #return the first character that are not canceled by the hashtag - #return inedx compared so far so we don't have to do that again - c = '' - hashtag = 0 - - while i>=0 and c=='': - char = s[i] - if char=='#': - hashtag+=1 - elif hashtag==0: - c = char - else: - hashtag-=1 - i-=1 - return c, i \ No newline at end of file diff --git a/problems/binary-search-tree-iterator.py b/problems/binary-search-tree-iterator.py deleted file mode 100644 index 2d171bd..0000000 --- a/problems/binary-search-tree-iterator.py +++ /dev/null @@ -1,36 +0,0 @@ -# Definition for a binary tree node. -# class TreeNode(object): -# def __init__(self, x): -# self.val = x -# self.left = None -# self.right = None - -class BSTIterator(object): - - def __init__(self, root): - self.stack = [] - while root: - self.stack.append(root) - root = root.left - - - - def next(self): - node = self.stack.pop() - r = node.right - while r: - self.stack.append(r) - r = r.left - return node.val - - - - def hasNext(self): - return len(self.stack)!=0 - - - -# Your BSTIterator object will be instantiated and called as such: -# obj = BSTIterator(root) -# param_1 = obj.next() -# param_2 = obj.hasNext() \ No newline at end of file diff --git a/problems/binary-tree-level-order-traversal-ii.py b/problems/binary-tree-level-order-traversal-ii.py deleted file mode 100644 index db49ccd..0000000 --- a/problems/binary-tree-level-order-traversal-ii.py +++ /dev/null @@ -1,20 +0,0 @@ -from collections import deque - -class Solution(object): - def levelOrderBottom(self, root): - opt = [] - - if not root: return opt - - q = deque() - q.append((root, 0)) - - while q: - node, depth = q.popleft() - if depth0 and options[i]==options[i-1]: continue -``` -Above lets us skip exploring the same path. -We can directly skip the `i` if the value is the same with `i-1`, because `dfs()` on `i-1` has already cover up all the possiblities. - -The time complexity is `O(N!)`. -The space complexity is `O(N!)`, too. -""" -class Solution(object): - def permuteUnique(self, nums): - def dfs(path, options): - if len(path)==len(nums): - opt.append(path) - return - for i, n in enumerate(options): - if i>0 and options[i]==options[i-1]: continue - dfs(path+[n], options[:i]+options[i+1:]) - opt = [] - nums.sort() - dfs([], nums) - return opt diff --git a/problems/permutations.py b/problems/permutations.py deleted file mode 100644 index 6659d57..0000000 --- a/problems/permutations.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -`nums = [1, 2, 3, 4, 5]` -The first time you choose you can either choose 1 or 2 or 3 or 4 or 5. Lets say you choose 2. So the path so far is `[2]`. -The second time you choose you can only choose 1 or 3 or 4 or 5. Lets say you choose 3. So the path so far is `[2, 3]`. -The third time you choose you can only choose 1 or 4 or 5. Lets say you choose 1. So the path so far is `[2, 3, 1]`. -The third time you choose you can only choose 4 or 5... -. -. -. - -We put the numbers we can choose in the `options` parameter. And the path so far in the `path` parameter. -In each `dfs()` we check if the path has used up all the numbers in the `nums`. If true. Append it in the output. -If not, we explore all the posible path in the `options` by `dfs()`. - -The time complexity is O(N!). Since in this example our choices is 5 at the beginning, then 4, then 3, then 2, then 1. -The space complexity is O(N!), too. And the recursion takes N level of recursion. -""" -class Solution(object): - def permute(self, nums): - def dfs(path, options): - if len(nums)==len(path): - opt.append(path) - return - for i, nums in enumerate(options): - dfs(path+[nums], options[:i]+options[i+1:]) - - opt = [] - dfs([], nums) - return opt diff --git a/problems/01-matrix.py b/problems/python/01-matrix.py old mode 100644 new mode 100755 similarity index 100% rename from problems/01-matrix.py rename to problems/python/01-matrix.py diff --git a/problems/python/3sum-closest.py b/problems/python/3sum-closest.py new file mode 100755 index 0000000..11b2dc1 --- /dev/null +++ b/problems/python/3sum-closest.py @@ -0,0 +1,24 @@ +class Solution(object): + def threeSumClosest(self, nums, target): + ans = float('inf') + N = len(nums) + + nums.sort() + + for i in xrange(N): + l = i+1 + r = N-1 + + while ltarget: + r -= 1 + elif s0, means that we need to reduce the s and it can only be done by decreasing k. +if s<0, means that we need to increase the s and it can only be done by increasing j. +""" +class Solution(object): + def threeSum(self, nums): + nums.sort() + ans = set() + N = len(nums) + + for i in xrange(N): + j = i+1 + k = N-1 + + while j0: + k -= 1 + elif s<0: + j += 1 + else: + ans.add(tuple(sorted([nums[i], nums[j], nums[k]]))) + k -= 1 + j += 1 + + return ans + +""" +Two Pointers +Time: O(N^2) +Space: O(1) + +Same as above. Move pointers to avoid repeation. Faster. +""" +class Solution(object): + def threeSum(self, nums): + nums.sort() + ans = [] + N = len(nums) + + for i in xrange(N): + j = i+1 + k = N-1 + + if i>0 and nums[i]==nums[i-1]: continue #[1] + + while j0: + k -= 1 + elif s<0: + j += 1 + else: + ans.append([nums[i], nums[j], nums[k]]) + + while jtarget: + d -= 1 + elif s0 and nums[a]==nums[a-1]: continue + for b in xrange(a+1, N): + if b>0 and nums[b]==nums[b-1] and a!=b-1: continue + c = b+1 + d = N-1 + + while ctarget: + d -= 1 + elif s O((N/M)*(logN-LogM)) +Time: O(MN) +Space: O(N) +""" +from collections import defaultdict + +class Solution(object): + def accountsMerge(self, accounts): + graph = defaultdict(list) + merged = set() + ans = [] + + #[0] + for data in accounts: + emails = data[1:] + for i, email in enumerate(emails): + graph[email].extend(emails[:i]) + graph[email].extend(emails[i+1:]) + + for data in accounts: + name = data[0] + visited = set() + stack = [data[1]] #[2] + + if data[1] in merged: continue #[1] + + while stack: + e = stack.pop() + if e in visited: continue + visited.add(e) + stack.extend(graph[e]) + + merged.update(visited) + ans.append([name]+sorted(list(visited))) #[3] + + return ans + + +#Union Find +class Solution(object): + def accountsMerge(self, accounts): + def find(x): + p = parents[x] + while p!=parents[p]: + p = find(p) + parents[x] = p + return p + + def union(x, y): + p1, p2 = find(x), find(y) + if p1==p2: return + parents[p2] = p1 + + parents = {} + mailToName = {} + + for account in accounts: + name = account[0] + root = account[1] + if root not in parents: parents[root] = root + root = find(root) + mailToName[root] = name + + for i in xrange(2, len(account)): + email = account[i] + if email in parents: + union(parents[email], root) + root = find(root) + parents[email] = root + + rootToMails = collections.defaultdict(list) + for email in parents: + rootToMails[find(email)].append(email) + + ans = [] + for root in rootToMails: + name = mailToName[root] + mails = rootToMails[root] + ans.append([name]+sorted(mails)) + + return ans + +#DFS +class Solution(object): + def accountsMerge(self, accounts): + + #build adjacency list + adj = collections.defaultdict(list) + for account in accounts: + name = account[0] + email0 = account[1] + for i in xrange(2, len(account)): + email = account[i] + adj[email0].append(email) + adj[email].append(email0) + + #iterate accounts and dfs each email group + ans = [] + visited = set() #store all the visited email + for account in accounts: + name = account[0] + email0 = account[1] + if email0 in visited: continue + + #dfs + group = set() #store the email group related to email0 + stack = [email0] + while stack: + email = stack.pop() + if email in group or email in visited: continue + group.add(email) + visited.add(email) + for nei in adj[email]: + stack.append(nei) + + ans.append([name]+sorted(list(group))) + + return ans + \ No newline at end of file diff --git a/problems/add-binary.py b/problems/python/add-binary.py old mode 100644 new mode 100755 similarity index 100% rename from problems/add-binary.py rename to problems/python/add-binary.py diff --git a/problems/add-digits.py b/problems/python/add-digits.py old mode 100644 new mode 100755 similarity index 100% rename from problems/add-digits.py rename to problems/python/add-digits.py diff --git a/problems/python/add-strings.py b/problems/python/add-strings.py new file mode 100755 index 0000000..9cdd7f9 --- /dev/null +++ b/problems/python/add-strings.py @@ -0,0 +1,34 @@ +class Solution(object): + def addStrings(self, nums1, nums2): + ans = '' + i = len(nums1)-1 + j = len(nums2)-1 + + carry = 0 + while 0<=i and 0<=j: + n1 = int(nums1[i]) + n2 = int(nums2[j]) + total = n1+n2+carry + n = total%10 + carry = 1 if total>=10 else 0 + ans = str(n)+ans + i -= 1 + j -= 1 + + while 0<=i: + total = int(nums1[i])+carry + n = total%10 + carry = 1 if total>=10 else 0 + ans = str(n)+ans + i -= 1 + + while 0<=j: + total = int(nums2[j])+carry + n = total%10 + carry = 1 if total>=10 else 0 + ans = str(n)+ans + j -= 1 + + if carry: ans = str(carry)+ans + + return ans \ No newline at end of file diff --git a/problems/add-two-numbers-ii.py b/problems/python/add-two-numbers-ii.py old mode 100644 new mode 100755 similarity index 100% rename from problems/add-two-numbers-ii.py rename to problems/python/add-two-numbers-ii.py diff --git a/problems/add-two-numbers.py b/problems/python/add-two-numbers.py old mode 100644 new mode 100755 similarity index 100% rename from problems/add-two-numbers.py rename to problems/python/add-two-numbers.py diff --git a/problems/python/alien-dictionary.py b/problems/python/alien-dictionary.py new file mode 100755 index 0000000..5c27918 --- /dev/null +++ b/problems/python/alien-dictionary.py @@ -0,0 +1,37 @@ +""" +Topological Sort +""" +class Solution(object): + def alienOrder(self, words): + #return true if cycles are detected. + def dfs(c): + if c in path: return True + if c in visited: return False + path.add(c) + for nei in adj[c]: + if dfs(nei): return True + res.append(c) + path.remove(c) + visited.add(c) + return False + + #build adjacency list + adj = {c: set() for word in words for c in word} + for i in xrange(len(words)-1): + w1, w2 = words[i], words[i+1] + minLen = min(len(w1), len(w2)) + if w1[:minLen]==w2[:minLen] and len(w1)>len(w2): return "" + + for j in xrange(minLen): + if w1[j]!=w2[j]: + adj[w1[j]].add(w2[j]) + break + + #topological sort + path = set() #path currently being reversed + visited = set() #done processing + res = [] + for c in adj: + if dfs(c): return "" + + return "".join(reversed(res)) \ No newline at end of file diff --git a/problems/python/all-nodes-distance-k-in-binary-tree.py b/problems/python/all-nodes-distance-k-in-binary-tree.py new file mode 100755 index 0000000..77152e3 --- /dev/null +++ b/problems/python/all-nodes-distance-k-in-binary-tree.py @@ -0,0 +1,40 @@ +class Solution(object): + def distanceK(self, root, target, k): + graph = collections.defaultdict(list) + q = collections.deque([root]) #for traverse binary tree + q2 = collections.deque([(target, k)]) #for bfs the graph + visited = set() #for bfs the graph + ans = [] + + #build graph + while q: + node = q.popleft() + + if node.left: + graph[node].append(node.left) + graph[node.left].append(node) + q.append(node.left) + + if node.right: + graph[node].append(node.right) + graph[node.right].append(node) + q.append(node.right) + + + #bfs graph + while q2: + node, distance = q2.popleft() + if node.val in visited: continue + visited.add(node.val) + if distance==0: ans.append(node.val) + if distance<0 or distance>k: continue + + for nei in graph[node]: + q2.append((nei, distance-1)) + + return ans + + + + + \ No newline at end of file diff --git a/problems/python/amount-of-new-area-painted-each-day.py b/problems/python/amount-of-new-area-painted-each-day.py new file mode 100755 index 0000000..a5504d5 --- /dev/null +++ b/problems/python/amount-of-new-area-painted-each-day.py @@ -0,0 +1,40 @@ +""" +1. Build sorted records = [(position, index, isStart)...] +2. Iterate through all positions and maintain a box with all the "index" of the records its position is in start ~ end +3. The smallest index in the box is the actual one that is paiting. + +Time: O(NLogN+P), N is the count of paint. Sorting the records takes NLogN. P is the max position. +Although there is a while loop when iterate through P, each record is only being iterated once. +O(NLogN + P + NLogN) ~= O(NLogN + P) +Space: O(N) +""" +from sortedcontainers import SortedList + +class Solution(object): + def amountPainted(self, paint): + ans = [0]*len(paint) + box = SortedList() + records = [] + maxPos = float('-inf') + + #[1] + for i, (start, end) in enumerate(paint): + records.append((start, i, -1)) + records.append((end, i, 1)) + maxPos = max(maxPos, end) + + records.sort() + + #[2] + i = 0 + for pos in xrange(maxPos+1): + while i3 or i>=len(websites): + return + else: + helper(comb+[websites[i]], i+1) + helper(comb[:], i+1) + + combs = set() + helper([], 0) + return combs + + + + \ No newline at end of file diff --git a/problems/python/backspace-string-compare.py b/problems/python/backspace-string-compare.py new file mode 100755 index 0000000..cec9e8a --- /dev/null +++ b/problems/python/backspace-string-compare.py @@ -0,0 +1,105 @@ +#https://leetcode.com/problems/backspace-string-compare/ +class Solution: + #Compare from backward. Because we need to count the hashtags first. + #Compare the char that are not canceled by the hashtags, letter by letter. + #So we don't have to convert the whole string if they are not the same. + #Return Fasle, as soon as we find out that they are not the same. + + def backspaceCompare(self, S1, S2): + #index compared so far + i = len(S1)-1 + j = len(S2)-1 + + while i>=0 or j>=0: + #the first char that are not canceled by the hashtags + c1 = '' + c2 = '' + + if i>=0: + c1, i = self.getChar(S1, i) + if j>=0: + c2, j = self.getChar(S2, j) + if c1!=c2: + return False + return True + + def getChar(self, s, i): + #return the first character that are not canceled by the hashtag + #return inedx compared so far so we don't have to do that again + c = '' + hashtag = 0 + + while i>=0 and c=='': + char = s[i] + if char=='#': + hashtag+=1 + elif hashtag==0: + c = char + else: + hashtag-=1 + i-=1 + return c, i +""" +Time: O(N) +Space: O(1) +""" +class Solution(object): + def backspaceCompare(self, s1, s2): + i = len(s1)-1 + j = len(s2)-1 + c1 = 0 #s1 unprocessed backspace count + c2 = 0 #s2 unprocessed backspace count + + while i>=0 or j>=0: + while i>=0: + if s1[i]=='#': + c1 += 1 + i -= 1 + elif c1>0: + c1 -= 1 + i -= 1 + else: + break + + while j>=0: + if s2[j]=='#': + c2 += 1 + j -= 1 + elif c2>0: + c2 -= 1 + j -= 1 + else: + break + + # if one of the string is finished, the other one should be finished, too. + if i<0: return j<0 + if j<0: return i<0 + + + if s1[i]!=s2[j]: return False + + i -= 1 + j -= 1 + + return True + + +class Solution(object): + def backspaceCompare(self, s, t): + def helper(S): + ans = '' + i = len(S)-1 + backspaceCount = 0 + + while i>=0: + if S[i]=='#': + backspaceCount += 1 + else: + if backspaceCount>0: + backspaceCount -= 1 + else: + ans += S[i] + i -= 1 + return ans + + return helper(s)==helper(t) \ No newline at end of file diff --git a/problems/python/balance-a-binary-search-tree.py b/problems/python/balance-a-binary-search-tree.py new file mode 100755 index 0000000..015934d --- /dev/null +++ b/problems/python/balance-a-binary-search-tree.py @@ -0,0 +1,36 @@ +""" +Imagine a list of sorted nodes, if we wanted to use the nodes to form a balanced BST, which root should we use? +Yes, the one in the middle, since it can evenly spread two groups of nodes. This is what `getRoot()` does. +And we do the same for the left half and right half. And so on. And so on... + +Time: O(N), N is the number of nodes. +Space: O(N) +""" +class Solution(object): + def balanceBST(self, root): + def getInorderNodes(root): + nodes = [] + stack = [] + node = root + + while node or stack: + while node: + stack.append(node) + node = node.left + + node = stack.pop() + nodes.append(node) + node = node.right + + return nodes + + def getRoot(l, r): + if l>r: return None + m = (l+r)/2 + root = inorderNodes[m] + root.left = getRoot(l, m-1) + root.right = getRoot(m+1, r) + return root + + inorderNodes = getInorderNodes(root) + return getRoot(0, len(inorderNodes)-1) \ No newline at end of file diff --git a/problems/balanced-binary-tree.py b/problems/python/balanced-binary-tree.py old mode 100644 new mode 100755 similarity index 53% rename from problems/balanced-binary-tree.py rename to problems/python/balanced-binary-tree.py index 3061e57..36232a8 --- a/problems/balanced-binary-tree.py +++ b/problems/python/balanced-binary-tree.py @@ -24,4 +24,25 @@ def helper(node, depth): return -1 if not root: return True - return helper(root, 0)!=-1 \ No newline at end of file + return helper(root, 0)!=-1 + + +""" +Time: O(N), since we recursively traverse each node once. +Space: O(LogN), for the recursive call. O(N) if the tree is not balanced. +""" +class Solution(object): + def isBalanced(self, root): + + #return (if the node isBalanced, the height of the node) + def helper(node): + if not node: return True, -1 + + isLeftBalanced, leftHeight = helper(node.left) + isRightBalanced, rightHeight = helper(node.right) + + height = max(leftHeight, rightHeight)+1 + + return isLeftBalanced and isRightBalanced and abs(leftHeight-rightHeight)<=1, height + + return helper(root)[0] \ No newline at end of file diff --git a/problems/python/basic-calculator-ii.py b/problems/python/basic-calculator-ii.py new file mode 100755 index 0000000..90ab539 --- /dev/null +++ b/problems/python/basic-calculator-ii.py @@ -0,0 +1,31 @@ +class Solution(object): + def calculate(self, s): + s += '+' #edge case, for last operation to be executed. + lastOperation = '+' #edge case, for the first currNum + operations = set(['+', '-', '*', '/']) + + stack = [] + currNum = 0 + + for c in s: + if c.isdigit(): + currNum = currNum*10 + int(c) + elif c in operations: + if lastOperation=='+': + stack.append(currNum) + currNum = 0 + elif lastOperation=='-': + stack.append(-currNum) + currNum = 0 + elif lastOperation=='*': + currNum = stack.pop() * currNum + stack.append(currNum) + currNum = 0 + elif lastOperation=='/': + currNum = stack.pop() / currNum + if currNum<0: currNum += 1 + stack.append(currNum) + currNum = 0 + lastOperation = c + + return sum(stack) \ No newline at end of file diff --git a/problems/best-time-to-buy-an-stock.py b/problems/python/best-time-to-buy-an-stock.py old mode 100644 new mode 100755 similarity index 100% rename from problems/best-time-to-buy-an-stock.py rename to problems/python/best-time-to-buy-an-stock.py diff --git a/problems/python/best-time-to-buy-and-sell-stock-iii.py b/problems/python/best-time-to-buy-and-sell-stock-iii.py new file mode 100755 index 0000000..f8f808c --- /dev/null +++ b/problems/python/best-time-to-buy-and-sell-stock-iii.py @@ -0,0 +1,28 @@ +""" +dp[i][0~3] = max profit in ith day in 0~3 state. + +0 hold the stock from 1th transaction. +1 sold the stock from 1th transaction. +2 hold the stock from 2nd transaction. +3 sold the stock from 2nd transaction. + +dp[i][0] = max(-prices[i], dp[i-1][0]) +dp[i][1] = max(dp[i-1][0]+prices[i], dp[i-1][1]) +dp[i][2] = max(dp[i-1][1]-prices[i], dp[i-1][2]) +dp[i][3] = max(dp[i-1][2]+prices[i], dp[i-1][3]) + +Time: O(N) +Space: O(N), can further reduce to O(1). +""" +class Solution(object): + def maxProfit(self, prices): + dp = [[0, 0, 0, 0] for _ in xrange(len(prices))] + dp[0] = [-prices[0], 0, -prices[0], 0] + + for i in xrange(1, len(prices)): + dp[i][0] = max(-prices[i], dp[i-1][0]) + dp[i][1] = max(dp[i-1][0]+prices[i], dp[i-1][1]) + dp[i][2] = max(dp[i-1][1]-prices[i], dp[i-1][2]) + dp[i][3] = max(dp[i-1][2]+prices[i], dp[i-1][3]) + + return max(dp[-1]) \ No newline at end of file diff --git a/problems/python/best-time-to-buy-and-sell-stock-with-cooldown.py b/problems/python/best-time-to-buy-and-sell-stock-with-cooldown.py new file mode 100755 index 0000000..896caf3 --- /dev/null +++ b/problems/python/best-time-to-buy-and-sell-stock-with-cooldown.py @@ -0,0 +1,47 @@ +class Solution(object): + def maxProfit(self, prices): + if not prices or len(prices)<=1: return 0 + N = len(prices) + + buy = [-prices[0], max(-prices[1], -prices[0])] + sell = [0, max(prices[1]+buy[0], 0)] + + for i in xrange(2, N): + buy.append(max(sell[i-2]-prices[i], buy[i-1])) + sell.append(max(prices[i]+buy[i-1], sell[i-1])) + + return max(buy[-1], sell[-1], 0) + +""" +Three rules: +1. Need to buy before sell. +2. After sell, next day need to rest (cannot buy). +3. When buying, we spend money, which is a negative profit. + +There are two possible end state. buy or sell. So we need to consider both. +Only considering prices 0~i, buy[i] stores the max profit that the last action is "buy". +Only considering prices 0~i, sell[i] stores the max profit that the last action is "sell". + +Let's sort this out. +When i==0: +buy: -prices[0] +sell: 0, since we cannot sell at i==0. + +When i==1: +buy: max(-prices[1], -prices[0]) +Now, we must not have sell yet. So no need to consider it. +If we buy at i==1, the profit will be `-prices[1]`. But we also had the option not to buy. buy[0] is the max profit if we don't buy at i==1. +Again, we are considering, when the end state is "buy", what is the max profit? +Thus, `max(-prices[1], buy[0])`. + +sell: max(prices[1]+buy[0], 0) +If we sell at i==1, the profit will be `prices[1]+buy[0]`. But we also had the option not to sell. 0 is the max profit if we don't sell at i==1. +Again, we are considering, when the end state is "sell", what is the max profit? +Thus, `max(prices[1]+buy[0], sell[0])` + +When i>=2: + + + + +""" \ No newline at end of file diff --git a/problems/python/best-time-to-buy-and-sell-stock.py b/problems/python/best-time-to-buy-and-sell-stock.py new file mode 100755 index 0000000..b4a9abc --- /dev/null +++ b/problems/python/best-time-to-buy-and-sell-stock.py @@ -0,0 +1,16 @@ +""" +To accomplish 0(N) solution: +For each i and price in the iteration, we need to keep track of the loest price before i. +Also, update the max_profit in the iteration. +""" +class Solution(object): + def maxProfit(self, prices): + if not prices: return 0 + max_profit = 0 + lowest = prices[0] + + for i in xrange(len(prices)): + if i==0: continue + max_profit = max(max_profit, prices[i]-lowest) + lowest = min(lowest, prices[i]) + return max_profit \ No newline at end of file diff --git a/problems/big-countries.sql b/problems/python/big-countries.sql old mode 100644 new mode 100755 similarity index 100% rename from problems/big-countries.sql rename to problems/python/big-countries.sql diff --git a/problems/python/binary-search-tree-iterator.py b/problems/python/binary-search-tree-iterator.py new file mode 100755 index 0000000..48d1458 --- /dev/null +++ b/problems/python/binary-search-tree-iterator.py @@ -0,0 +1,88 @@ +class BSTIterator(object): + + def __init__(self, root): + self.stack = [] + while root: + self.stack.append(root) + root = root.left + + + + def next(self): + node = self.stack.pop() + r = node.right + while r: + self.stack.append(r) + r = r.left + return node.val + + + + def hasNext(self): + return len(self.stack)!=0 + +""" +Time: init() O(1). next() O(LogN). hasNext() O(1). +Space: O(N) +""" +class BSTIterator(object): + + def __init__(self, root): + self.node = root + self.stack = [] + + def next(self): + while self.node: + self.stack.append(self.node) + self.node = self.node.left + + node = self.stack.pop() + self.node = node.right + return node.val + + def hasNext(self): + return self.stack or self.node + + +""" +FYI. Template for in-order traverse. +""" +def inOrderTraverse(root): + stack = [] + node = root + + while node or stack: + while node: + stack.append(node) + node = node.left + + node = stack.pop() + + #do something + print node.val + + node = node.right + + + +class BSTIterator(object): + + def __init__(self, root): + self.stack = [] + self.node = root + + + + def next(self): + while self.node: + self.stack.append(self.node) + self.node = self.node.left + self.node = self.stack.pop() + returnVal = self.node.val + self.node = self.node.right + return returnVal + + + + def hasNext(self): + return self.node or self.stack \ No newline at end of file diff --git a/problems/binary-search.py b/problems/python/binary-search.py old mode 100644 new mode 100755 similarity index 100% rename from problems/binary-search.py rename to problems/python/binary-search.py diff --git a/problems/python/binary-subarrays-with-sum.py b/problems/python/binary-subarrays-with-sum.py new file mode 100755 index 0000000..dafb077 --- /dev/null +++ b/problems/python/binary-subarrays-with-sum.py @@ -0,0 +1,19 @@ +class Solution(object): + def numSubarraysWithSum(self, nums, goal): + #number of subarrays with sum at most "goal" + def atMost(goal): + ans = 0 + total = 0 + i = 0 + + for j, num in enumerate(nums): + if num==1: total += 1 + + while igoal: + if nums[i]==1: total -= 1 + i += 1 + + ans += j-i+1 #number of subarrays that can generate from nums[i~j] + return ans + + return atMost(goal)-atMost(goal-1) if goal>0 else atMost(goal) \ No newline at end of file diff --git a/problems/binary-tree-inorder-traversal.py b/problems/python/binary-tree-inorder-traversal.py old mode 100644 new mode 100755 similarity index 100% rename from problems/binary-tree-inorder-traversal.py rename to problems/python/binary-tree-inorder-traversal.py diff --git a/problems/python/binary-tree-level-order-traversal-ii.py b/problems/python/binary-tree-level-order-traversal-ii.py new file mode 100755 index 0000000..07084a3 --- /dev/null +++ b/problems/python/binary-tree-level-order-traversal-ii.py @@ -0,0 +1,67 @@ +from collections import deque + +class Solution(object): + def levelOrderBottom(self, root): + opt = [] + + if not root: return opt + + q = deque() + q.append((root, 0)) + + while q: + node, depth = q.popleft() + if depth'+str(node.val)) if path else str(node.val) + + if node.left: q.append((node.left, path)) + if node.right: q.append((node.right, path)) + + if not node.left and not node.right: ans.append(path) + return ans \ No newline at end of file diff --git a/problems/binary-tree-pruning.py b/problems/python/binary-tree-pruning.py old mode 100644 new mode 100755 similarity index 100% rename from problems/binary-tree-pruning.py rename to problems/python/binary-tree-pruning.py diff --git a/problems/binary-tree-right-side-view.py b/problems/python/binary-tree-right-side-view.py old mode 100644 new mode 100755 similarity index 61% rename from problems/binary-tree-right-side-view.py rename to problems/python/binary-tree-right-side-view.py index ddae3a5..76d807f --- a/problems/binary-tree-right-side-view.py +++ b/problems/python/binary-tree-right-side-view.py @@ -28,4 +28,28 @@ def rightSideView(self, root): view.append(node.val) queue.append((node.right, level+1)) queue.append((node.left, level+1)) - return view \ No newline at end of file + return view + +""" +Time: O(N). +Space: O(N). + +Similar solution using DFS. +""" +class Solution(object): + def rightSideView(self, root): + if not root: return [] + + ans = [] + currLevel = -1 + stack = [(root, 0)] + + while stack: + node, level = stack.pop() + if level>currLevel: + ans.append(node.val) + currLevel = level + if node.left: stack.append((node.left, level+1)) + if node.right: stack.append((node.right, level+1)) + + return ans \ No newline at end of file diff --git a/problems/python/binary-tree-vertical-order-traversal.py b/problems/python/binary-tree-vertical-order-traversal.py new file mode 100755 index 0000000..1510ac0 --- /dev/null +++ b/problems/python/binary-tree-vertical-order-traversal.py @@ -0,0 +1,50 @@ +""" +Time: O(N) +Space: O(N) + +Use BFS to traverse the tree. +x is the x coordinate of the node, the smaller the x is the leftmost node will be. +Since we add the left node first, the nodes "in the same row and column" will automatically sorted from left to right. +""" +class Solution(object): + def verticalOrder(self, root): + if not root: return [] + + q = collections.deque([(root, 0)]) + minX = 0 + maxX = 0 + ans = collections.defaultdict(list) + + while q: + node, x = q.popleft() + ans[x].append(node.val) + minX = min(minX, x) + maxX = max(maxX, x) + + if node.left: q.append((node.left, x-1)) + if node.right: q.append((node.right, x+1)) + + return [ans[x] for x in xrange(minX, maxX+1)] + + +class Solution(object): + def verticalOrder(self, root): + if not root: return [] + ans = [] + minX = float('inf') + maxX = float('-inf') + locations = collections.defaultdict(list) + q = collections.deque([(root, 0)]) + + while q: + node, x = q.popleft() + locations[x].append(node.val) + minX = min(minX, x) + maxX = max(maxX, x) + if node.left: q.append((node.left, x-1)) + if node.right: q.append((node.right, x+1)) + + for x in xrange(minX, maxX+1): + if locations[x]: ans.append(locations[x]) + + return ans \ No newline at end of file diff --git a/problems/python/binary-watch.py b/problems/python/binary-watch.py new file mode 100755 index 0000000..616758d --- /dev/null +++ b/problems/python/binary-watch.py @@ -0,0 +1,14 @@ +""" +Time: O(1) +Space: O(1), no "extra" space are used. +""" +class Solution(object): + def readBinaryWatch(self, turnedOn): + ans = [] + if turnedOn>8: return ans #at most 8 LED are turned on for a valid time. + + for h in xrange(12): + for m in xrange(60): + if (bin(h) + bin(m)).count('1')==turnedOn: + ans.append('%d:%02d' % (h, m)) + return ans \ No newline at end of file diff --git a/problems/python/buildings-with-an-ocean-view.py b/problems/python/buildings-with-an-ocean-view.py new file mode 100755 index 0000000..0025b13 --- /dev/null +++ b/problems/python/buildings-with-an-ocean-view.py @@ -0,0 +1,16 @@ +""" +Traverse from the right and keep track of the highest building. + +Time: O(N) +Space: O(1) +""" +class Solution(object): + def findBuildings(self, heights): + ans = [] + currMaxHeight = 0 + for i in xrange(len(heights)-1, -1, -1): + h = heights[i] + if h>currMaxHeight: ans.append(i) + currMaxHeight = max(currMaxHeight, h) + + return reversed(ans) \ No newline at end of file diff --git a/problems/python/burst-balloons.py b/problems/python/burst-balloons.py new file mode 100755 index 0000000..97d37aa --- /dev/null +++ b/problems/python/burst-balloons.py @@ -0,0 +1,28 @@ +""" +dp[i][j] := max coins gains from nums[i+1:j] +Assume k is the last balloon that get burst within nums[i:j+1], try different k and get the max dp[i][j]. + +max coins gains from nums[i+1:j] will be the sum of +1. max coins gains from nums[i:k]: `dp[i][k-1] if k-1>=0 else 0` +2. coins gains from bursring k: `(nums[i-1] if 0<=i-1 else 1) * nums[k] * (nums[j+1] if j+1=N: continue + for k in xrange(i, j+1): + dp[i][j] = max(dp[i][j], + (dp[i][k-1] if k-1>=0 else 0) + + (nums[i-1] if 0<=i-1 else 1) * nums[k] * (nums[j+1] if j+1=N: return cost + + for i in xrange(M): + if state[i]=='1': continue + + nextState = state[:i] + '1' + state[i+1:] + if nextState in visited: continue + heapq.heappush(pq, (cost+costs[i][j], nextState)) + + return float('inf') \ No newline at end of file diff --git a/problems/python/candy.py b/problems/python/candy.py new file mode 100755 index 0000000..e77a631 --- /dev/null +++ b/problems/python/candy.py @@ -0,0 +1,21 @@ +class Solution(object): + def candy(self, ratings): + N = len(ratings) + + l2r = [1]*N + r2l = [1]*N + + for i in xrange(1, N): + if ratings[i]>ratings[i-1]: + l2r[i] = l2r[i-1]+1 + + for i in xrange(N-2, -1, -1): + if ratings[i]>ratings[i+1]: + r2l[i] = r2l[i+1]+1 + + ans = 0 + for i in xrange(N): + ans += max(l2r[i], r2l[i]) + + return ans + \ No newline at end of file diff --git a/problems/capacity-to-ship-packages-within-d-days.py b/problems/python/capacity-to-ship-packages-within-d-days.py old mode 100644 new mode 100755 similarity index 71% rename from problems/capacity-to-ship-packages-within-d-days.py rename to problems/python/capacity-to-ship-packages-within-d-days.py index 8444e0a..e396aa6 --- a/problems/capacity-to-ship-packages-within-d-days.py +++ b/problems/python/capacity-to-ship-packages-within-d-days.py @@ -18,15 +18,17 @@ def shipWithinDays(self, weights, D): if daily_weight: d += 1 if d>D: - #K cannot be the answer. - #next round we don't need to put K in l~r. + #c cannot be the answer. + #next round we don't need to put c in l~r. l = c+1 else: - #K might ot might not be the answer. - #next round we still need to put K in l~r. + #c might ot might not be the answer. + #next round we still need to put c in l~r. r = c return l + + """ This is a binary search problem. If you do not understand binary search yet, please study it first. @@ -43,6 +45,12 @@ def shipWithinDays(self, weights, D): [2] So the boundary of our answer, `l` and `r`, will collides together (`l==r`) and jump out of the loop. -Time complexity: `O(NlogN)`. There will be `O(LogN)` iteration. For every iteration we need O(N) to calculate the `t`. `N` is the length of `piles`. -Space complexity is O(N). For calculating `t`. +Time complexity: `O(NlogW)`. +`N` is the number of `weights`. +`W` is the max weight. +There will be `O(LogW)` iteration. For every iteration we need O(N) to calculate the `d`. +Space complexity is O(1). + +Also take a look at problem, 875, very similar. +https://leetcode.com/problems/koko-eating-bananas/discuss/750699/ """ \ No newline at end of file diff --git a/problems/cheapest-flights-within-k-stops.py b/problems/python/cheapest-flights-within-k-stops.py old mode 100644 new mode 100755 similarity index 62% rename from problems/cheapest-flights-within-k-stops.py rename to problems/python/cheapest-flights-within-k-stops.py index f5e8f0c..eb785b3 --- a/problems/cheapest-flights-within-k-stops.py +++ b/problems/python/cheapest-flights-within-k-stops.py @@ -23,16 +23,19 @@ class Solution1(object): def findCheapestPrice(self, n, flights, src, dst, K): graph = collections.defaultdict(list) pq = [] + visited = set() for u, v, w in flights: graph[u].append((w, v)) heapq.heappush(pq, (0, K+1, src)) while pq: price, stops, city = heapq.heappop(pq) + visited.add(city) if city is dst: return price if stops>0: for price_to_nei, nei in graph[city]: + if nei in visited: continue heapq.heappush(pq, (price+price_to_nei, stops-1, nei)) return -1 @@ -66,3 +69,37 @@ def findCheapestPrice(self, n, flights, src, dst, K): return min_price if min_price!=float('inf') else -1 + +""" +Standard Dijkstra, except this time instead of only explore the ones with least price +We also need to explore the ones with less steps. So add stepFromSrc to check. + +Time: O(ELogE), since there will be at most E edges that get pushed into the heap. +Space: O(E) +""" +class Solution(object): + def findCheapestPrice(self, n, flights, src, dst, K): + priceFromSrc = {} + stepFromSrc = {} + h = [(0, 0, src)] + G = collections.defaultdict(list) + + #build graph + for s, d, p in flights: + G[s].append((d, p)) + + #dijkstra + while h: + price, k, node = heapq.heappop(h) + + if node==dst: return price + if k>K: continue + + for nei, price2 in G[node]: + #explore next destination with less price or less steps + if nei not in priceFromSrc or price+price2<=priceFromSrc[nei] or k2: + #combination count of n stairs equals to + #(the combination after you make 1 step as first move) + (the combination after you make 2 steps as first move) + history[n] = helper(n-1) + helper(n-2) + + return history[n] + + history = {} + return helper(n) \ No newline at end of file diff --git a/problems/clone-graph.py b/problems/python/clone-graph.py old mode 100644 new mode 100755 similarity index 76% rename from problems/clone-graph.py rename to problems/python/clone-graph.py index b311acf..b82f0f8 --- a/problems/clone-graph.py +++ b/problems/python/clone-graph.py @@ -54,8 +54,28 @@ def cloneGraph(self, start): return clone[start] - - +#2020/8/7, similar to 2 +class Solution(object): + def cloneGraph(self, node): + if not node: return node + + visited = set() + clones = {} + stack = [] + + stack.append(node) + while stack: + curr = stack.pop() + if curr in visited: continue + visited.add(curr) + clones[curr] = Node(curr.val) + stack.extend(curr.neighbors) + + for curr in clones: + clones[curr].neighbors = [clones[c] for c in curr.neighbors] + + return clones[node] + diff --git a/problems/python/closest-binary-search-tree-value.py b/problems/python/closest-binary-search-tree-value.py new file mode 100755 index 0000000..ca3c644 --- /dev/null +++ b/problems/python/closest-binary-search-tree-value.py @@ -0,0 +1,24 @@ +""" +Time: O(LogN) +Space: O(1) + +Basically we are going to search the target in the BST. +Along the way, we compare it with the `ans`, if the difference is smaller, update it. +""" +class Solution(object): + def closestValue(self, root, target): + node = root + ans = float('inf') + + while node: + if not node: break + + if abs(ans-target)>abs(node.val-target): + ans = node.val + + if target>node.val: + node = node.right + else: + node = node.left + + return ans \ No newline at end of file diff --git a/problems/python/coin-change.py b/problems/python/coin-change.py new file mode 100755 index 0000000..2c0d299 --- /dev/null +++ b/problems/python/coin-change.py @@ -0,0 +1,39 @@ +# DP +class Solution(object): + def coinChange(self, coins, amount): + dp = [float('inf')]*(amount+1) + + if amount==0: return 0 + + dp[0] = 0 + for coin in coins: + if coin<=amount: + dp[coin] = 1 + + for a in xrange(amount+1): + for coin in coins: + if a-coin>=0: + dp[a] = min(dp[a], dp[a-coin]+1) + + return dp[amount] if dp[amount]!=float('inf') else -1 + +# BFS +import collections +class Solution(object): + def coinChange(self, coins, amount): + visited = set() + + coins.sort(reverse=True) + q = collections.deque([(0, 0)]) + + while q: + current_amount, count = q.popleft() + + if current_amount==amount: return count + if current_amount>amount: continue + if current_amount in visited: continue + visited.add(current_amount) + + for coin in coins: + q.append((current_amount+coin, count+1)) + return -1 \ No newline at end of file diff --git a/problems/python/coloring-a-border.py b/problems/python/coloring-a-border.py new file mode 100755 index 0000000..0b1ecc9 --- /dev/null +++ b/problems/python/coloring-a-border.py @@ -0,0 +1,43 @@ +""" +A node is on the "border" if +1. It is on the actual border of the grid. +Or +2. Any of its neighbor has different color. + +BFS the grid starting from (row, col), if the node "isBorder" add it to nodesToColor. +Color the nodes in the nodesToColor. + +Time: O(N) +Space: O(N) +""" +class Solution(object): + def colorBorder(self, grid, row, col, color): + def isValid(i, j): + return i>=0 and j>=0 and istart and candidates[i]==candidates[i-1]: continue + candidate = candidates[i] + comb.append(candidate) + helper(remain-candidate, comb, i+1) + comb.pop() + + ans = [] + candidates.sort() + helper(target, [], 0) + return ans + + + \ No newline at end of file diff --git a/problems/combination-sum-iii.py b/problems/python/combination-sum-iii.py old mode 100644 new mode 100755 similarity index 54% rename from problems/combination-sum-iii.py rename to problems/python/combination-sum-iii.py index c5fa010..d5fccc1 --- a/problems/combination-sum-iii.py +++ b/problems/python/combination-sum-iii.py @@ -38,3 +38,40 @@ def dfs(path, min_num): dfs([], 1) return opt + + +""" +Use a helper to check the remain. +comb is the combination to sum up to remain. +k is the max length of comb. +start is the starting index of "nums", since we already explore the all the possible comb from previous index. + +Time: O(9!/(9-k)! * k), 9*8*7... for k times. And each ans takes O(k) to copy the comb list. +Space: O(k) +""" +class Solution(object): + def combinationSum3(self, k, n): + def helper(remain, comb, k, start): + if remain==0 and len(comb)==k: + ans.append(comb[:]) + elif remain<0 or len(comb)>k: + return + else: + for i in xrange(start, len(nums)): + used = nums[i] + num = i+1 + + if used: continue + + comb.append(num) + nums[i] = True + + helper(remain-num, comb, k, i+1) + + comb.pop() + nums[i] = False + + nums = [False]*9 + ans = [] + helper(n, [], k, 0) + return ans \ No newline at end of file diff --git a/problems/python/combination-sum-iv.py b/problems/python/combination-sum-iv.py new file mode 100755 index 0000000..88bc853 --- /dev/null +++ b/problems/python/combination-sum-iv.py @@ -0,0 +1,66 @@ +class Solution(object): + def combinationSum4(self, nums, target): + def helper(t): + if t<0: return 0 + if dp[t]>=0: return dp[t] + + ans = 0 + for num in nums: + ans += helper(t-num) + + dp[t] = ans + return ans + + dp = [-1]*(target+1) + dp[0] = 1 + + for i in xrange(target+1): + helper(i) + return dp[target] + +""" +Time: O(TN), T is the value of target and N is the count of nums. +Space: O(T) +""" + + +""" +dp[n] := number of combs sums up to n + +For example, lets say target is 32 and nums is [4,2,1]. +dp[32] = dp[28]+dp[30]+dp[31] +Since the combs of dp[28] adds 4 will all equals to 32. +Since the combs of dp[30] adds 2 will all equals to 32. +Since the combs of dp[31] adds 1 will all equals to 32. + +... + +dp[28] = dp[24]+dp[26]+dp[27] +Since the combs of dp[28] adds 4 will all equals to 24. +Since the combs of dp[28] adds 2 will all equals to 26. +Since the combs of dp[28] adds 1 will all equals to 27. + +... + + + +Time: O(TN), T is the value of target and N is the count of nums. +Space: O(T) +""" +class Solution(object): + def combinationSum4(self, nums, target): + dp = [0]*(target+1) + dp[0] = 1 + + t = 1 + + while t<=target: + combs = 0 + for num in nums: + if t-num<0: continue + combs += dp[t-num] + + dp[t] = combs + t += 1 + + return dp[target] \ No newline at end of file diff --git a/problems/combination-sum.py b/problems/python/combination-sum.py old mode 100644 new mode 100755 similarity index 84% rename from problems/combination-sum.py rename to problems/python/combination-sum.py index 423f3c1..c76cc6a --- a/problems/combination-sum.py +++ b/problems/python/combination-sum.py @@ -118,3 +118,26 @@ def dfs(index, target, path): candidates.sort() dfs(0, T, []) return opt + + +""" +Time: O(N^(T/M)), N is the number of candidates. T is target. M is min(candidates). +Space: O(T/M) +""" +class Solution(object): + def combinationSum(self, candidates, target): + def helper(remain, comb, start=0): + if remain==0: + ans.append(comb[:]) + elif remain<0: + return + elif remain>0: + for i in xrange(start, len(candidates)): + candidate = candidates[i] + comb.append(candidate) + helper(remain-candidate, comb, i) + comb.pop() + + ans = [] + helper(target, []) + return ans \ No newline at end of file diff --git a/problems/combinations.py b/problems/python/combinations.py old mode 100644 new mode 100755 similarity index 87% rename from problems/combinations.py rename to problems/python/combinations.py index 49f7ebf..f3a0937 --- a/problems/combinations.py +++ b/problems/python/combinations.py @@ -83,6 +83,21 @@ def dfs(n_min, path): +class Solution(object): + def combine(self, N, K): + def dfs(comb, start, N, K): + if len(comb)==K: ans.append(comb[:]) + + for n in xrange(start, N+1): + comb.append(n) + dfs(comb, n+1, N, K) + comb.pop() + + ans = [] + dfs([], 1, N, K) + return ans + + diff --git a/problems/combine-two-tables.sql b/problems/python/combine-two-tables.sql old mode 100644 new mode 100755 similarity index 100% rename from problems/combine-two-tables.sql rename to problems/python/combine-two-tables.sql diff --git a/problems/compare-version-numbers.py b/problems/python/compare-version-numbers.py old mode 100644 new mode 100755 similarity index 100% rename from problems/compare-version-numbers.py rename to problems/python/compare-version-numbers.py diff --git a/problems/python/connecting-cities-with-minimum-cost.py b/problems/python/connecting-cities-with-minimum-cost.py new file mode 100755 index 0000000..50a04e5 --- /dev/null +++ b/problems/python/connecting-cities-with-minimum-cost.py @@ -0,0 +1,38 @@ +class Solution(object): + def minimumCost(self, N, connections): + def union(n1, n2): + p1 = find(n1) + p2 = find(n2) + + if p1==p2: return False + + if ranks[p1]>ranks[p2]: + parents[p2] = p1 + ranks[p1] += 1 + else: + parents[p1] = p2 + ranks[p2] += 1 + return True + + def find(n): + p = parents[n] + while p!=parents[p]: p = find(p) + parents[n] = p + return p + + if not connections: return 0 + + parents = [n for n in xrange(N+1)] + ranks = [0]*(N+1) + totalCost = 0 + count = N # the count of nodes not yet union + + # sort by cost, union the ones with less cost first + sortedConnections = sorted([(cost, x, y) for x, y, cost in connections]) + + for cost, x, y in sortedConnections: + if union(x, y): + totalCost += cost + count -= 1 + + return totalCost if count==1 else -1 \ No newline at end of file diff --git a/problems/python/consecutive-numbers-sum.py b/problems/python/consecutive-numbers-sum.py new file mode 100755 index 0000000..127faa6 --- /dev/null +++ b/problems/python/consecutive-numbers-sum.py @@ -0,0 +1,43 @@ + +# Sliding window +class Solution(object): + def consecutiveNumbersSum(self, N): + i = 1 + j = 2 + s = 1 #sum(range(i, j)) + ans = 0 + + while i<=j and j<=N+1: + if s==N: + ans += 1 + s += j + j += 1 + elif s>N: + s -= i + i += 1 + else: + s += j + j += 1 + return ans + + +#Math +class Solution(object): + def consecutiveNumbersSum(self, N): + ans = 0 + upperLimit = int((2 * N + 0.25)**0.5 - 0.5) + 2 + + for x in xrange(1, upperLimit): + if x%2==0: + if float(N)/x-N/x==0.5: + upper = N/x + x/2 + lower = N/x - x/2 + 1 + if 1<=lower and upper<=N: + ans += 1 + else: + if N%x==0: + upper = N/x + (x-1)/2 + lower = N/x - (x-1)/2 + if 1<=lower and upper<=N: + ans += 1 + return ans \ No newline at end of file diff --git a/problems/python/construct-binary-tree-from-inorder-and-postorder-traversal.py b/problems/python/construct-binary-tree-from-inorder-and-postorder-traversal.py new file mode 100755 index 0000000..f8304fc --- /dev/null +++ b/problems/python/construct-binary-tree-from-inorder-and-postorder-traversal.py @@ -0,0 +1,102 @@ +""" +inorder: [LEFT]root[RIGHT] +postorder: [LEFT][RIGHT]root + +First thing we know is the value of root, which is the last element of `postorder`. +Find the index of the root in `inorder`. So find out the interval of [LEFT] and [RIGHT] in `inorder`. + +The length of the [LEFT] and [RIGHT] in `inorder` are the same with the length of the [LEFT] and [RIGHT] in `postorder`. +""" +class Solution(object): + def buildTree(self, inorder, postorder): + if not inorder or not postorder: return None + + root = TreeNode(postorder[-1]) + if len(inorder)==1: return root + + r = inorder.index(root.val) + + leftInOrder = inorder[:r] + leftPostOrder = postorder[:r] + rightInOrder = inorder[r+1:] + rightPostOrder = postorder[r:len(postorder)-1] + + root.left = self.buildTree(leftInOrder, leftPostOrder) + root.right = self.buildTree(rightInOrder, rightPostOrder) + + return root +""" +Time: O(NLogN). For each node, we need to do an iteration to its children. To be precise.. +O(N) for constructing root. + +O(N/2) for constructing root.left +O(N/2) for constructing root.right + +O(N/4) for constructing root.left.left +O(N/4) for constructing root.left.right +O(N/4) for constructing root.right.left +O(N/4) for constructing root.right.right +... + +To improve this, we can use a hash table to get the index of `i` below + + +Space: O(NLogN). +For each node, we need to construct inorder/postorder arrays of its children. +We can improve this by using pointers. +""" + +""" +Improved version. +Time: O(N). +Space: O(N). For `index`. +""" +class Solution(object): + def buildTree(self, inorder, postorder): + def helper(i, j, k, l): + if j-i<=0: return None + if l-k<=0: return None + + root = TreeNode(postorder[l-1]) + if j-i==1: return root + + r = index[root.val] + + root.left = helper(i, r, k, k+r-i) + root.right = helper(r+1, j, k+r-i, l-1) + return root + + index = {} #the index of inorder + for i, n in enumerate(inorder): index[n] = i + return helper(0, len(inorder), 0, len(postorder)) + + + + + +""" +Inorder: [LEFT]root[RIGHT] +Postorder: [LEFT][RIGHT]root +As you can see, rootVal is the last val in postorder. + +i and j is the range of the inorder +k and l is the range of the postorder +Get the root +Also recursively use the same method to get the left and right node, but this time we change the inorder and postorder. +""" +class Solution(object): + def buildTree(self, inorder, postorder): + def helper(i, j, k, l): + if i>j or k>l: return None + rootVal = postorder[l] + root = TreeNode(rootVal) + r = inorderIndex[rootVal] + leftLength = r-i + rightLength = j-r + root.left = helper(i, r-1, k, k+leftLength-1) + root.right = helper(r+1, j, k+leftLength, k+leftLength+rightLength-1) + return root + + inorderIndex = {} + for i, n in enumerate(inorder): inorderIndex[n] = i + return helper(0, len(inorder)-1, 0, len(postorder)-1) diff --git a/problems/python/construct-binary-tree-from-preorder-and-inorder-traversal.py b/problems/python/construct-binary-tree-from-preorder-and-inorder-traversal.py new file mode 100755 index 0000000..3f021d7 --- /dev/null +++ b/problems/python/construct-binary-tree-from-preorder-and-inorder-traversal.py @@ -0,0 +1,30 @@ +""" +Time: O(N) +Space: O(N) + +preorder: root[LEFT][RIGHT] +inorder: [LEFT]root[RIGHT] + +The `helper()` will return the root node from `preorder` and `inorder`. +If they have left or right child, call `helper()` again to get the left or right node. + +Use pointers to represent array, avoiding keep copying `preorder` and `inorder`. +i and j represent the preorder[i:j] +k and l epresent the inorder[k:l] +""" +class Solution(object): + def buildTree(self, preorder, inorder): + def helper(i, j, k, l): + root = TreeNode(preorder[i]) + + r = inorderIndex[root.val] #the index of the root val in inorder + leftLength = r-k + rightLength = l-(r+1) + + if leftLength>0: root.left = helper(i+1, i+1+leftLength, k, k+leftLength) + if rightLength>0: root.right = helper(i+1+leftLength, i+1+leftLength+rightLength, r+1, r+1+rightLength) + return root + + inorderIndex = {} + for i, n in enumerate(inorder): inorderIndex[n] = i + return helper(0, len(preorder), 0, len(inorder)) \ No newline at end of file diff --git a/problems/container-with-most-water.py b/problems/python/container-with-most-water.py old mode 100644 new mode 100755 similarity index 70% rename from problems/container-with-most-water.py rename to problems/python/container-with-most-water.py index e5f49f4..6f7a531 --- a/problems/container-with-most-water.py +++ b/problems/python/container-with-most-water.py @@ -31,3 +31,21 @@ def maxArea(self, H): else: r = r-1 return ans + + +class Solution(object): + def maxArea(self, height): + i = 0 + j = len(height)-1 + ans = 0 + + while ik: + bidToRemove = nums[i-k-1]//(t+1) + del bucket[bidToRemove] + + bid = num//(t+1) + if bid in bucket: return True + if bid+1 in bucket and abs(bucket[bid+1]-num)<=t: return True + if bid-1 in bucket and abs(bucket[bid-1]-num)<=t: return True + bucket[bid] = num + + return False \ No newline at end of file diff --git a/problems/python/contains-duplicate.py b/problems/python/contains-duplicate.py new file mode 100755 index 0000000..a27739c --- /dev/null +++ b/problems/python/contains-duplicate.py @@ -0,0 +1,13 @@ +""" +Time: O(N) +Space: O(N) +""" +class Solution(object): + def containsDuplicate(self, nums): + seen = set() + + for num in nums: + if num in seen: return True + seen.add(num) + + return False \ No newline at end of file diff --git a/problems/python/continuous-subarray-sum.py b/problems/python/continuous-subarray-sum.py new file mode 100755 index 0000000..75dd08b --- /dev/null +++ b/problems/python/continuous-subarray-sum.py @@ -0,0 +1,35 @@ +class Solution(object): + def checkSubarraySum(self, nums, k): + prefixSumEndings = collections.defaultdict(list) + prefixSumEndings[0].append(-1) + + prefixSum = 0 + for i, n in enumerate(nums): + prefixSum += n + + x = prefixSum/k + while x>=0: + p2 = prefixSum-k*x + if len(prefixSumEndings[p2])>0 and prefixSumEndings[p2][0]=1: return True + if len(prefixSumKRemain[remain])>0 and prefixSumKRemain[remain][0]0: node.left = helper(i, m) + if rightLength>0: node.right = helper(m+1, j) + return node + + return helper(0, len(nums)) + + + + +class Solution(object): + def sortedArrayToBST(self, nums): + def helper(s, e): + if s>e: return None + m = (s+e)/2 + node = TreeNode(nums[m]) + node.left = helper(s, m-1) + node.right = helper(m+1, e) + return node + return helper(0, len(nums)-1) \ No newline at end of file diff --git a/problems/python/convert-sorted-list-to-binary-search-tree.py b/problems/python/convert-sorted-list-to-binary-search-tree.py new file mode 100755 index 0000000..5b127b7 --- /dev/null +++ b/problems/python/convert-sorted-list-to-binary-search-tree.py @@ -0,0 +1,45 @@ +""" +Time: O(NLogN), +The root need to traverse N node to get its midNode. + +The root.left need to traverse N/2 node to get its midNode. +The root.right need to traverse N/2 node to get its midNode. + +The root.left.left need to traverse N/4 node to get its midNode. +The root.left.right need to traverse N/4 node to get its midNode. +The root.right.left need to traverse N/4 node to get its midNode. +The root.right.right need to traverse N/4 node to get its midNode. + +... +So total: +N + (N/2+N/2) + (N/4+N/4+N/4+N/4) + ... => N + N + N + ... +There are LogN levels in the tree, so LogN x N => NLogN + +Space: O(LogN) for recursion depth. +""" +class Solution(object): + def sortedListToBST(self, head): + if not head: return None + + midNode = self.getMiddle(head) + root = TreeNode(midNode.val) + + if head.val==midNode.val: return root + + root.left = self.sortedListToBST(head) + root.right = self.sortedListToBST(midNode.next) + + return root + + def getMiddle(self, node): + pre = None + slow = node + fast = node + + while fast and fast.next: + fast = fast.next.next + pre = slow + slow = slow.next + + if pre: pre.next = None + return slow \ No newline at end of file diff --git a/problems/python/copy-list-with-random-pointer.py b/problems/python/copy-list-with-random-pointer.py new file mode 100755 index 0000000..ddf901e --- /dev/null +++ b/problems/python/copy-list-with-random-pointer.py @@ -0,0 +1,92 @@ +# Definition for singly-linked list with a random pointer. +# class RandomListNode(object): +# def __init__(self, x): +# self.label = x +# self.next = None +# self.random = None +class Solution(object): + def copyRandomList(self, head): + if head==None: return head + + curr = head + while curr: + temp = curr.next + new_node = RandomListNode(curr.label) + new_node.next = temp + curr.next = new_node + curr = curr.next.next + + curr = head + while curr: + curr.next.random = curr.random.next if curr.random else None + curr = curr.next.next + + curr = head + curr_copy = head.next + head_copy = head.next + + while curr: + curr.next = curr.next.next + curr_copy.next = curr_copy.next.next if curr_copy.next else None + curr = curr.next + curr_copy = curr_copy.next + + return head_copy + +# 2020/8/11 +class Solution(object): + def copyRandomList(self, head): + if not head: return head + + clones = {} + + curr = head + while curr: + clones[curr] = Node(curr.val) + curr = curr.next + + curr = head + while curr: + if curr.next: clones[curr].next = clones[curr.next] + if curr.random: clones[curr].random = clones[curr.random] + curr = curr.next + + return clones[head] + +""" +Time: O(N). Two iteration. First iteration make a the clones. Second iteration setup the links. +Space: O(N). +""" + + +""" +Time: O(N) +Space: O(1) +""" +class Solution(object): + def copyRandomList(self, head): + if not head: return head + curr = head + while curr: + nextCurr = curr.next + copy = Node(curr.val) + curr.next = copy + copy.next = nextCurr + curr = nextCurr + + copyHead = head.next + + curr = head + while curr: + if curr.random: curr.next.random = curr.random.next + curr = curr.next.next + + curr = head + while curr: + nextCurr = curr.next.next + if nextCurr: + curr.next.next = nextCurr.next + curr.next = nextCurr + curr = nextCurr + + return copyHead \ No newline at end of file diff --git a/problems/python/count-all-valid-pickup-and-delivery-opti.py b/problems/python/count-all-valid-pickup-and-delivery-opti.py new file mode 100755 index 0000000..8bfe82a --- /dev/null +++ b/problems/python/count-all-valid-pickup-and-delivery-opti.py @@ -0,0 +1,5 @@ +class Solution(object): + def countOrders(self, n): + d = 1 + for i in xrange(1, 2*n, 2): d*=i + return math.factorial(n)*d % (10**9 + 7) \ No newline at end of file diff --git a/problems/python/count-binary-substrings.py b/problems/python/count-binary-substrings.py new file mode 100755 index 0000000..5a2520e --- /dev/null +++ b/problems/python/count-binary-substrings.py @@ -0,0 +1,21 @@ +class Solution(object): + def countBinarySubstrings(self, s): + groups = [] + count = 0 + last = s[0] + ans = 0 + + for bit in s: + if bit==last: + count += 1 + else: + groups.append(count) + count = 1 + last = bit + + groups.append(count) + + for i in xrange(1, len(groups)): + ans += min(groups[i], groups[i-1]) + + return ans \ No newline at end of file diff --git a/problems/python/count-complete-tree-nodes.py b/problems/python/count-complete-tree-nodes.py new file mode 100755 index 0000000..ab9c7bc --- /dev/null +++ b/problems/python/count-complete-tree-nodes.py @@ -0,0 +1,27 @@ +""" +Time: O(H^2). The time complexity will countNodes() will be LogH if the tree is perfect. +If not, we will need Log(H-1) + +Space: O(H) +""" +class Solution(object): + def countNodes(self, root): + if not root: return 0 + + node = root + l = 1 #level on the right side + while node.left: + node = node.left + l += 1 + + node = root + r = 1 #level on the left side + while node.right: + node = node.right + r += 1 + + if l==r: + #perfect tree + return (2**r)-1 + else: + return 1+self.countNodes(root.left)+self.countNodes(root.right) \ No newline at end of file diff --git a/problems/python/count-number-of-nice-subarrays.py b/problems/python/count-number-of-nice-subarrays.py new file mode 100755 index 0000000..33df804 --- /dev/null +++ b/problems/python/count-number-of-nice-subarrays.py @@ -0,0 +1,19 @@ +class Solution(object): + def numberOfSubarrays(self, nums, K): + def atMost(k): + oddCount = 0 + ans = 0 + i = 0 + + for j in xrange(len(nums)): + if nums[j]%2!=0: oddCount += 1 + + while oddCount>k: + if nums[i]%2!=0: oddCount -= 1 + i += 1 + + ans += j-i+1 + + return ans + + return atMost(K)-atMost(K-1) \ No newline at end of file diff --git a/problems/python/count-unique-characters-of-all-substrings-of-a-given-string.py b/problems/python/count-unique-characters-of-all-substrings-of-a-given-string.py new file mode 100755 index 0000000..fe58877 --- /dev/null +++ b/problems/python/count-unique-characters-of-all-substrings-of-a-given-string.py @@ -0,0 +1,18 @@ +class Solution(object): + def uniqueLetterString(self, s): + index = {} + ans = 0 + for c in 'abcdefghijklmnopqrstuvwxyz': + index[c.upper()] = [-1, -1] + + # count the substring that s[j] is the unique letter + for k, c in enumerate(s): + i, j = index[c] + ans += (j-i) * (k-j) + index[c] = [j, k] + + # count the substring that s[j] is the unique letter, because last iteration did not count the last letter + for c in index: + i, j = index[c] + ans += (j-i) * (len(s)-j) + return ans \ No newline at end of file diff --git a/problems/python/course-schedule-ii.py b/problems/python/course-schedule-ii.py new file mode 100755 index 0000000..a7c139f --- /dev/null +++ b/problems/python/course-schedule-ii.py @@ -0,0 +1,76 @@ +from collections import defaultdict, deque + +class Solution(object): + def findOrder(self, numCourses, prerequisites): + graph = defaultdict(list) + inbound = defaultdict(int) + q = deque() + order = deque() + + #building graph as adjacency list + for c1, c2 in prerequisites: + graph[c2].append(c1) + inbound[c1] += 1 + + #find the starting point + for c in xrange(numCourses): + if inbound[c]==0: + q.append(c) + + #traverse the directed graph + while q: + c = q.popleft() + + for nei in graph[c]: + inbound[nei] -= 1 + if inbound[nei]==0: + q.append(nei) + + order.append(c) + + return order if len(order)==numCourses else [] + +""" +Topological sort works only in directed graph. +We can use it to know which node comes after which or detect cycles. +The algorithm is easy to understand. +First, we build the adjacent list (`graph`) and count all the inbound of the node. +Then we start from the node whose inbound count is 0, adding it in to the `pq` (priority queue). +For every node we pop out from `pq` + * We remove the node's outbound by decrease 1 on all its neighbor's inbound. + * Put the node's neighbor to `pq` if it has no inbound + * Put the node into the `sortedNodes` +Repeat the process until there is no more node. +The order in the `sortedNodes` is the order we are going to encounter when we run through the directed graph. +If we cannot sort all the nodes in the graph, it means that there are some nodes we couldn't find its starting point, in other words, there are cycles in the graph. + +Time: O(E+2V) ~= O(E+V) +we used O(E) to build the graph #[1], O(V) to find the starting point #[2], then traverse all the nodes again #[3]. +Space: O(E+3V) ~= O(E+V), O(E+V) for the adjacent list. O(V) for the `q`, O(V) for the `q_next`. +""" +class Solution(object): + def findOrder(self, numCourses, prerequisites): + graph = collections.defaultdict(list) + inbounds = collections.defaultdict(int) + pq = collections.deque() + sortedNodes = [] + + for c1, c2 in prerequisites: + graph[c2].append(c1) + inbounds[c1]+=1 + + for node in xrange(numCourses): + if inbounds[node]==0: + pq.append(node) + + while pq: + node = pq.popleft() + + for nei in graph[node]: + inbounds[nei] -= 1 + if inbounds[nei]==0: + pq.append(nei) + sortedNodes.append(node) + + return sortedNodes if len(sortedNodes)==numCourses else [] + \ No newline at end of file diff --git a/problems/course-schedule.py b/problems/python/course-schedule.py old mode 100644 new mode 100755 similarity index 99% rename from problems/course-schedule.py rename to problems/python/course-schedule.py index 9121ee2..3e05d6d --- a/problems/course-schedule.py +++ b/problems/python/course-schedule.py @@ -1,23 +1,3 @@ -""" -First, we build a graph of adjacency list #[0] -0->[2,4,5] -1->[3,4] -Meaning before taking 2,4,5 we need to take 0, before taking 3,4 we need to take 1 -if we find a loop back to itself then it is impossible, for example -0->[2,4,5] -1->[3,4] -2->[0,3] -0->2->0, which is imposible. - -Now we iterate every course to see if it can loop back to itself in anyway #[1] -we do this by dfs and search for it self -if we find itself we find loop - -The time efficiency is O(V^2+VE), because each dfs in adjacency list is O(V+E) and we do it V times -Space efficiency is O(E). -V is the numCourses (Vertices). -E is the number of prerequisites (Edges). -""" class Solution(object): def canFinish(self, numCourses, prerequisites): graph = {n:[] for n in xrange(numCourses)} #[0] @@ -36,25 +16,27 @@ def canFinish(self, numCourses, prerequisites): if ajc not in visited: stack.append(ajc) return True - """ -Topological sort works only in directed graph. -We can use it to know which node comes after which or detect cycles. -The algorithm is easy to understand. -First, we build the adjacent list and count all the inbound of the node. -Then we start from the node whose inbound count is 0, adding it in to the `q_next`. -For every node we pop out from q_next - * We remove the node's outbound by decrease 1 on all its neighbor's inbound. - * Put the node's neighbor to `q_next` if it has no inbound - * Put the node into the `q` -Repeat the process until there is no more node. -The order in the `q` is the order we are going to encounter when we run through the directed graph. -If we cannot sort all the nodes in the graph, it means that there are some nodes we couldn't find its starting point, in other words, there are cycles in the graph. +First, we build a graph of adjacency list #[0] +0->[2,4,5] +1->[3,4] +Meaning before taking 2,4,5 we need to take 0, before taking 3,4 we need to take 1 +if we find a loop back to itself then it is impossible, for example +0->[2,4,5] +1->[3,4] +2->[0,3] +0->2->0, which is imposible. -Time: O(E+2V) ~= O(E+V) -we used O(E) to build the graph #[1], O(V) to find the starting point #[2], then traverse all the nodes again #[3]. -Space: O(E+3V) ~= O(E+V), O(E+V) for the adjacent list. O(V) for the `q`, O(V) for the `q_next`. +Now we iterate every course to see if it can loop back to itself in anyway #[1] +we do this by dfs and search for it self +if we find itself we find loop + +The time efficiency is O(V^2+VE), because each dfs in adjacency list is O(V+E) and we do it V times +Space efficiency is O(E). +V is the numCourses (Vertices). +E is the number of prerequisites (Edges). """ + class Solution(object): def canFinish(self, numCourses, prerequisites): graph = collections.defaultdict(list) @@ -78,5 +60,22 @@ def canFinish(self, numCourses, prerequisites): q_next.append(nei) q.append(n) return len(q)==numCourses + +""" +Topological sort works only in directed graph. +We can use it to know which node comes after which or detect cycles. +The algorithm is easy to understand. +First, we build the adjacent list and count all the inbound of the node. +Then we start from the node whose inbound count is 0, adding it in to the `q_next`. +For every node we pop out from q_next + * We remove the node's outbound by decrease 1 on all its neighbor's inbound. + * Put the node's neighbor to `q_next` if it has no inbound + * Put the node into the `q` +Repeat the process until there is no more node. +The order in the `q` is the order we are going to encounter when we run through the directed graph. +If we cannot sort all the nodes in the graph, it means that there are some nodes we couldn't find its starting point, in other words, there are cycles in the graph. - +Time: O(E+2V) ~= O(E+V) +we used O(E) to build the graph #[1], O(V) to find the starting point #[2], then traverse all the nodes again #[3]. +Space: O(E+3V) ~= O(E+V), O(E+V) for the adjacent list. O(V) for the `q`, O(V) for the `q_next`. +""" \ No newline at end of file diff --git a/problems/python/custom-sort-string.py b/problems/python/custom-sort-string.py new file mode 100755 index 0000000..2e63523 --- /dev/null +++ b/problems/python/custom-sort-string.py @@ -0,0 +1,20 @@ +""" +Take a look at the char in s. +For the char that is in the order, rearrange them to sortedChars with respect to the "order". +For the char that is in not the order, put them in postString. +""" +class Solution(object): + def customSortString(self, order, s): + sortedChars = '' + counter = collections.Counter(s) + for c in order: + if c in order: + sortedChars += c*counter[c] + + orderSet = set(order) + postString = '' + for c in s: + if c not in orderSet: + postString += c + + return sortedChars+postString \ No newline at end of file diff --git a/problems/python/cutting-ribbons.py b/problems/python/cutting-ribbons.py new file mode 100755 index 0000000..175e336 --- /dev/null +++ b/problems/python/cutting-ribbons.py @@ -0,0 +1,13 @@ +class Solution(object): + def maxLength(self, ribbons, k): + maxLen = max(ribbons) + minLen = 0 + + while minLen=k: + minLen = l + else: + maxLen = l-1 + return maxLen \ No newline at end of file diff --git a/problems/python/data-stream-as-disjoint-intervals.py b/problems/python/data-stream-as-disjoint-intervals.py new file mode 100755 index 0000000..50f47b4 --- /dev/null +++ b/problems/python/data-stream-as-disjoint-intervals.py @@ -0,0 +1,91 @@ +class SummaryRanges(object): + + def __init__(self): + self.nums = [] + + + def addNum(self, val): + bisect.insort_left(self.nums, val) + + + def getIntervals(self): + ans = [] + + for num in self.nums: + if not ans: + ans.append([num, num]) + elif ans[len(ans)-1][1]+1==num: + ans[len(ans)-1][1] = num + elif ans[len(ans)-1][1]0 and self.ans[i-1][1]+1==self.ans[i][0]: + self.ans[i][0] = self.ans[i-1][0] + self.ans.pop(i-1) + + return True + + elif val==self.ans[i][1]+1: + self.ans[i][1] = val + + #merge i and i+1 if needed + if i+1=len(s): return 1 + if s[i]=='0': return 0 + + count = 0 + count += helper(s, i+1) + if len(s)-i>=2 and int(s[i:i+2])<=26: count += helper(s, i+2) + memo[(s, i)] = count + return count + + memo = {} + return helper(s, 0) \ No newline at end of file diff --git a/problems/python/delete-and-earn.py b/problems/python/delete-and-earn.py new file mode 100755 index 0000000..90804f3 --- /dev/null +++ b/problems/python/delete-and-earn.py @@ -0,0 +1,14 @@ +import collections + +class Solution(object): + def deleteAndEarn(self, nums): + if not nums: return 0 + m = min(nums) + M = max(nums) + c = collections.Counter(nums) + + prev = 0 + curr = 0 + for n in xrange(m, M+1): + prev, curr = curr, max(prev+n*c[n], curr) + return curr \ No newline at end of file diff --git a/problems/python/delete-duplicate-folders-in-system.py b/problems/python/delete-duplicate-folders-in-system.py new file mode 100755 index 0000000..512cd1a --- /dev/null +++ b/problems/python/delete-duplicate-folders-in-system.py @@ -0,0 +1,42 @@ +class Node(object): + def __init__(self, val): + self.val = val + self.key = None + self.children = {} + +class Solution(object): + def deleteDuplicateFolder(self, paths): + def setKey(node): + node.key = '' + for c in sorted(node.children.keys()): #need to be sorted. so when child structs are the same, we won't generate different key from different iteration order. + setKey(node.children[c]) + node.key += node.children[c].val + '|' + node.children[c].key + '|' #generate a key for each node. only considering its children structure. (see the "identical" definition, it does not consider the val of the node itself.) + + keyCount[node.key] += 1 + + def addPath(node, path): + if node.children and keyCount[node.key]>1: return #leaf node does not apply to this rule + ans.append(path+[node.val]) + for c in node.children: + addPath(node.children[c], path+[node.val]) + + + ans = [] + root = Node('/') + keyCount = collections.Counter() + + #build the tree + for path in paths: + node = root + for c in path: + if c not in node.children: node.children[c] = Node(c) + node = node.children[c] + + #set all nodes key recursively + setKey(root) + + #build ans + for c in root.children: + addPath(root.children[c], []) + + return ans \ No newline at end of file diff --git a/problems/python/delete-node-in-a-bst.py b/problems/python/delete-node-in-a-bst.py new file mode 100755 index 0000000..f1e0fe2 --- /dev/null +++ b/problems/python/delete-node-in-a-bst.py @@ -0,0 +1,99 @@ +""" +Find the parant and the node to be deleted. [0] +Deleting the node means replacing its reference by something else. [1] + +For the node to be deleted, if it only has no child, just remove it from the parent. Return None. [2] + +If it has one child, return the child. So its parent will directly connect to its child. [3] + +If it has both child. Update the node's val to the minimum value in the right subtree. Remove the minimum value node in the right subtree. [4] +This is equivalent to replacing the node by the minimum value node in the right subtree. +Another option is to replace the node by the maximum value node in the left subtree. + +Find the minimum value in the left subtree is easy. The leftest node value in the tree is the smallest. [5] + +Time Complexity: O(LogN). O(LogN) for finding the node to be deleted. +The recursive call in `remove()` will be apply to a much smaller subtree. And much smaller subtree... +So can be ignored. +Space complexity is O(LogN). Because the recursive call will at most be called LogN times. +N is the number of nodes. And LogN can be consider the height of the tree. +""" +class Solution(object): + def deleteNode(self, root, key): + def find_min(root): + curr = root + while curr.left: curr = curr.left + return curr.val#[5] + + def remove(node): + if node.left and node.right: + node.val = find_min(node.right) + node.right = self.deleteNode(node.right, node.val) + return node #[4] + elif node.left and not node.right: + return node.left #[3] + elif node.right and not node.left: + return node.right #[3] + else: + return None #[2] + + if not root: return root + node = root + + while node: #[0] + if node.val==key: + return remove(node) #[1] + elif node.left and node.left.val==key: + node.left = remove(node.left) #[1] + return root + elif node.right and node.right.val==key: + node.right = remove(node.right) #[1] + return root + + if key>node.val and node.right: + node = node.right + elif keynode.val: + node.right = self.deleteNode(node.right, key) + return node + elif key==node.val: + if not node.left and not node.right: + return None + elif not node.left: + return node.right + elif not node.right: + return node.left + else: + node.val = self.findMin(node.right) #min value in the right subtree + node.right = self.deleteNode(node.right, node.val) + return node + + def findMin(self, root): + ans = float('inf') + stack = [root] + + while stack: + node = stack.pop() + ans = min(ans, node.val) + if node.left: stack.append(node.left) + if node.right: stack.append(node.right) + return ans + + + diff --git a/problems/python/delete-operation-for-two-strings.py b/problems/python/delete-operation-for-two-strings.py new file mode 100755 index 0000000..24ddeb1 --- /dev/null +++ b/problems/python/delete-operation-for-two-strings.py @@ -0,0 +1,28 @@ +class Solution(object): + def minDistance(self, word1, word2): + N = len(word1) + M = len(word2) + + dp = [[float('inf') for _ in xrange(M+1)] for _ in xrange(N+1)] + + + for i in xrange(1, N+1): dp[i][0] = i #need i deletion for word1[0:i-1] to be "" + for j in xrange(1, M+1): dp[0][j] = j #need i deletion for word1[0:j-1] to be "" + dp[0][0] = 0 + + for i in xrange(1, N+1): + for j in xrange(1, M+1): + #dp[i][j] := min operation count for dp[0:i] and dp[0:j-1] to be the same. + + #if char at i-1 and j-1 are the same, no deletion needed. + if word1[i-1]==word2[j-1]: + dp[i][j] = min(dp[i][j], dp[i-1][j-1]) + + dp[i][j] = min(dp[i][j], dp[i][j-1]+1) #remove char on word2[j-1] + dp[i][j] = min(dp[i][j], dp[i-1][j]+1) #remove char on word1[i-1] + return dp[N][M] + +""" +Time: O(MN) +Space: O(MN) +""" \ No newline at end of file diff --git a/problems/python/design-add-and-search-words-data-structure.py b/problems/python/design-add-and-search-words-data-structure.py new file mode 100755 index 0000000..4b0d71f --- /dev/null +++ b/problems/python/design-add-and-search-words-data-structure.py @@ -0,0 +1,35 @@ +class Node(object): + def __init__(self, char): + self.char = char + self.children = {} + +class WordDictionary(object): + + def __init__(self): + self.root = Node('') + self.endSign = ';' + + def addWord(self, word): + word = word + self.endSign + node = self.root + for c in word: + if c not in node.children: + node.children[c] = Node(c) + node = node.children[c] + + def search(self, word): + word = word + self.endSign + return self.searchFromNode(self.root, word) + + def searchFromNode(self, node, word): + if not word: return True + + char = word[0] + if char in node.children: + return self.searchFromNode(node.children[char], word[1:]) + elif char=='.': + for c in node.children: + if self.searchFromNode(node.children[c], word[1:]): + return True + return False + \ No newline at end of file diff --git a/problems/python/design-in-memory-file-system.py b/problems/python/design-in-memory-file-system.py new file mode 100755 index 0000000..3e9d808 --- /dev/null +++ b/problems/python/design-in-memory-file-system.py @@ -0,0 +1,46 @@ +""" +self.dirs is a nested hashmap to store the file structure. +self.files is a hashmap, mainly used to determine if the path is a "file", also used to store the content of the file. + +Time: +ls O(K) +mkdir O(K) +addContentToFile O(K) +readContentFromFile O(1) +K is the path count, for example /a/b/c/d, K=4. + +Space: +O(N), N is the dir counts. +""" +class FileSystem(object): + + def __init__(self): + self.files = {} + self.dirs = {} + + def ls(self, path): + if path in self.files: + return [path.split('/')[-1]] + else: + if path=='/': return sorted(self.dirs.keys()) + curr = self.dirs + for d in path.split('/')[1:]: + curr = curr[d] + return sorted(curr.keys()) + + def mkdir(self, path): + curr = self.dirs + for d in path.split('/')[1:]: + if d not in curr: + curr[d] = {} + curr = curr[d] + + def addContentToFile(self, filePath, content): + if filePath not in self.files: + self.mkdir(filePath) + self.files[filePath] = content + else: + self.files[filePath] += content + + def readContentFromFile(self, filePath): + return self.files[filePath] \ No newline at end of file diff --git a/problems/design-linked-list.py b/problems/python/design-linked-list.py old mode 100644 new mode 100755 similarity index 100% rename from problems/design-linked-list.py rename to problems/python/design-linked-list.py diff --git a/problems/python/design-tic-tac-toe.py b/problems/python/design-tic-tac-toe.py new file mode 100755 index 0000000..5e72cda --- /dev/null +++ b/problems/python/design-tic-tac-toe.py @@ -0,0 +1,52 @@ +""" +Time: O(1) for move(). +Space: O(N). + +We need to find a way to quikly check if the player wins after the move. +Checking the whole map row by row or column by column will take a lots of time. +What we do instead is to record the count of the placement for each row and colomn. Also the count of topright-bottomleft, topleft-bottomright placement. +So if any of the count adds up to n, the player wins. + +self.records[0] stores player1's record. +self.records[1] stores player2's record. + +For player1's record (self.records[0]), +record[0][row] stores the placement count of the row for player1. +record[1][col] stores the placement count of the col for player1. +record[2] stores the topright-bottomleft placement count for player1. +record[3] stores the topleft-bottomright placement count for player1. + +For player2's record (self.records[1]), +record[0][row] stores the placement count of the row for player2. +record[1][col] stores the placement count of the col for player2. +record[2] stores the topright-bottomleft placement count for player2. +record[3] stores the topleft-bottomright placement count for player2. + +Note that, when checkRecord() we only need to check the (row, col) we just placed. +So we can achieve O(1) in time. +""" +class TicTacToe(object): + + def __init__(self, n): + self.records = [[[0]*n, [0]*n, 0, 0], [[0]*n, [0]*n, 0, 0]] + self.n = n + + + def move(self, row, col, player): + record = self.records[player-1] + record[0][row] += 1 + record[1][col] += 1 + if row==col: record[2] += 1 + if row+col==self.n-1: record[3] += 1 + + if self.checkRecord(record, row, col): return player + + return 0 + + def checkRecord(self, record, row, col): + if record[0][row]==self.n: return True + if record[1][col]==self.n: return True + if record[2]==self.n: return True + if record[3]==self.n: return True + + return False \ No newline at end of file diff --git a/problems/python/diagonal-traverse.py b/problems/python/diagonal-traverse.py new file mode 100755 index 0000000..0510201 --- /dev/null +++ b/problems/python/diagonal-traverse.py @@ -0,0 +1,25 @@ +class Solution(object): + def findDiagonalOrder(self, mat): + def helper(i, j, reverse): + output = [] + while 0<=ishelf_width: break + + h = max(h, books[i][1]) + if i==0: + dp[j] = min(dp[j], h) + else: + dp[j] = min(dp[j], dp[i-1]+h) + return dp[-1] + + +""" +dp[i] := smallest height that the last book on book shelve is books[i] +dp[i] = min { dp[j]+max(books[j+1:i]) where sumWidth(books[j+1:i])<=W, j = 0~i-1} +""" +class Solution(object): + def minHeightShelves(self, books, W): + dp = [float('inf')]*len(books) + + dp[0] = books[0][1] + + for i in xrange(1, len(books)): + topLevelWidth = 0 + topLevelMaxHeight = 0 + + for j in xrange(i, -1, -1): + topLevelWidth += books[j][0] + topLevelMaxHeight = max(topLevelMaxHeight, books[j][1]) + if topLevelWidth>W: break + dp[i] = min(dp[i], (dp[j-1] if j-1>=0 else 0) + topLevelMaxHeight) + + return dp[-1] + \ No newline at end of file diff --git a/problems/python/find-all-possible-recipes-from-given-supplies.py b/problems/python/find-all-possible-recipes-from-given-supplies.py new file mode 100755 index 0000000..13772ac --- /dev/null +++ b/problems/python/find-all-possible-recipes-from-given-supplies.py @@ -0,0 +1,33 @@ +""" +Use Topological Sort +1. Build an adj list and inbound. +2. Starts from supplies topologically traverse the map. +3. For each node popping up, if it is in recipes, add it to ans. + +Time: O(N), N is the number of "nodes" (ingredients+recipes) +Space: O(N). +""" +class Solution(object): + def findAllRecipes(self, recipes, ingredients, supplies): + N = len(recipes) + adj = collections.defaultdict(list) + inbounds = collections.Counter() + q = collections.deque(supplies) + recipeSet = set(recipes) + ans = [] + + for i, recipe in enumerate(recipes): + for ingredient in ingredients[i]: + adj[ingredient].append(recipe) + inbounds[recipe] += 1 + + while q: + node = q.popleft() + + if node in recipeSet: ans.append(node) + + for nei in adj[node]: + inbounds[nei] -= 1 + if inbounds[nei]==0: q.append(nei) + + return ans \ No newline at end of file diff --git a/problems/python/find-and-replace-in-string.py b/problems/python/find-and-replace-in-string.py new file mode 100755 index 0000000..4d74203 --- /dev/null +++ b/problems/python/find-and-replace-in-string.py @@ -0,0 +1,51 @@ +""" +`p` is the index on s which already processed. +""" +class Solution(object): + def findReplaceString(self, s, indices, sources, targets): + p = -1 + ans = '' + + memo = {} + for i, index in enumerate(indices): + memo[index] = (sources[i], targets[i]) + + for i in xrange(len(s)): + if i<=p: continue + + if i in memo and (s[i:i+len(memo[i][0])] if i+len(memo[i][0])=2 as lca. + def findCount(node): + if not node: return 0 + count = 0 + if node.val==p or node.val==q: count += 1 + count += findPQCount(node.left) + count += findPQCount(node.right) + if count>=2 and not self.lca: self.lca = node + return count + + def findHeight(node, h): + if not node: return + if node.val==p: self.pHeight = h + if node.val==q: self.qHeight = h + if self.pHeight and self.qHeight: return + findHeight(node.left, h+1) + findHeight(node.right, h+1) + + findCount(root) + findHeight(self.lca, 0) + return self.qHeight + self.pHeight \ No newline at end of file diff --git a/problems/python/find-duplicate-subtrees.py b/problems/python/find-duplicate-subtrees.py new file mode 100755 index 0000000..5729e8e --- /dev/null +++ b/problems/python/find-duplicate-subtrees.py @@ -0,0 +1,16 @@ +class Solution(object): + def findDuplicateSubtrees(self, root): + def dfs(node): + if not node: return '#' + string = str(node.val) + ',' + dfs(node.left) + ',' + dfs(node.right) + data[string].append(node) + return string + + data = collections.defaultdict(list) + ans = [] + + dfs(root) + + for s in data: + if len(data[s])>=2: ans.append(data[s][0]) + return ans \ No newline at end of file diff --git a/problems/python/find-eventual-safe-states.py b/problems/python/find-eventual-safe-states.py new file mode 100755 index 0000000..ab0c1c1 --- /dev/null +++ b/problems/python/find-eventual-safe-states.py @@ -0,0 +1,29 @@ +from collections import defaultdict, deque + +class Solution(object): + def eventualSafeNodes(self, graph): + inbounds = defaultdict(list) + outbondsCounter = defaultdict(int) + q = deque() + ans = [] + + for n, nei_list in enumerate(graph): + outbondsCounter[n] = len(nei_list) + for nei in nei_list: + inbounds[nei].append(n) + + for n in outbondsCounter: + if outbondsCounter[n]==0: + q.append(n) + + while q: + n = q.popleft() + + for nei in inbounds[n]: + outbondsCounter[nei] -= 1 + if outbondsCounter[nei]==0: + q.append(nei) + + ans.append(n) + + return ans.sort() \ No newline at end of file diff --git a/problems/find-first-and-last-position-of-element-in-sorted-array.py b/problems/python/find-first-and-last-position-of-element-in-sorted-array.py old mode 100644 new mode 100755 similarity index 75% rename from problems/find-first-and-last-position-of-element-in-sorted-array.py rename to problems/python/find-first-and-last-position-of-element-in-sorted-array.py index c9495dc..513dda2 --- a/problems/find-first-and-last-position-of-element-in-sorted-array.py +++ b/problems/python/find-first-and-last-position-of-element-in-sorted-array.py @@ -86,7 +86,39 @@ def find_range(i, nums): +#2021/7/23 +""" +Time: O(LogN). Worse case: 0(N). +Space: O(1) - +Standard binary search. Find the first and last by `rangeOf()` after we found it. +""" +class Solution(object): + def searchRange(self, A, T): + def rangeOf(i): + l = r = i + while 0=len(arr) or (l>=0 and isCloserThan(arr[l], arr[r], X)): + output1.append(arr[l]) + l -= 1 + else: + output2.append(arr[r]) + r += 1 + + return output1[::-1]+output2 diff --git a/problems/python/find-k-pairs-with-smallest-sums.py b/problems/python/find-k-pairs-with-smallest-sums.py new file mode 100755 index 0000000..85ab26c --- /dev/null +++ b/problems/python/find-k-pairs-with-smallest-sums.py @@ -0,0 +1,38 @@ +""" +Time: O(kLogk) +Space: O(N) + +[0] +Declare a min-heap. Which stores the (sum, index in nums1, index in nums2) for each combination. + +[1] +Each iteration, we get a min sum (i, j) from the heap and add it to the `ans`. +The next possible smallest combination will be either (i+1, j) or (j+1, i) or other combination previously added. +Add combination (i+1, j) and (j+1, i) to the heap. + +[2] +Use a set `seen` to avoid adding the same element to the heap. +For example (i+1, j) and (j+1, i) can both lead to (i+1, j+1). +""" +class Solution(object): + def kSmallestPairs(self, nums1, nums2, k): + if not nums1 or not nums2: return [] + if k>len(nums1)*len(nums2): k = len(nums1)*len(nums2) + + ans = [] + h = [(nums1[0]+nums2[0], 0, 0)] #[0] + seen = set([(0,0)]) #[2] + + while len(ans)1: + heapq.heappush(self.larger, -heapq.heappop(self.smaller)) + elif len(self.larger)-len(self.smaller)>=1: + heapq.heappush(self.smaller, -heapq.heappop(self.larger)) + + def findMedian(self): + return -self.smaller[0] if (len(self.smaller)-len(self.larger))%2!=0 else (-self.smaller[0]+self.larger[0])/2.0 \ No newline at end of file diff --git a/problems/find-minimum-in-rotated-sorted-array-ii.py b/problems/python/find-minimum-in-rotated-sorted-array-ii.py old mode 100644 new mode 100755 similarity index 53% rename from problems/find-minimum-in-rotated-sorted-array-ii.py rename to problems/python/find-minimum-in-rotated-sorted-array-ii.py index 7467070..ffa07d7 --- a/problems/find-minimum-in-rotated-sorted-array-ii.py +++ b/problems/python/find-minimum-in-rotated-sorted-array-ii.py @@ -27,3 +27,38 @@ def findMin(self, nums): l = l+1 r = r-1 return m + + +""" +Time: O(LogN). Worse Case: O(N). +Space: O(1) + +The key idea for most rotated array question is that +If you cut a rotated array into half, +One of the half will be rotated and one half will be in-order. The smallest one must be in the rotated one. +""" +class Solution(object): + def findMin(self, A): + N = len(A) + l = 0 + r = N-1 + + while lnums[m]: + #l~m is unsorted + r = m + else: + #m~r is unsorted + #in this case, we already sure that current m is not the MIN, so we can +1 to speed up and avoid infinitive loop + l = m+1 \ No newline at end of file diff --git a/problems/python/find-mode-in-binary-search-tree.py b/problems/python/find-mode-in-binary-search-tree.py new file mode 100755 index 0000000..b4d3aff --- /dev/null +++ b/problems/python/find-mode-in-binary-search-tree.py @@ -0,0 +1,110 @@ +""" +Time: O(N). For we traverse every node recursively. +Space: O(N). Becuase we store all element's val and count in `counter`. +Note that, we do not use the feature of BST. +""" +from collections import Counter +class Solution(object): + def findMode(self, root): + def count(node): + counter[node.val] += 1 + if node.left: count(node.left) + if node.right: count(node.right) + + if not root: return [] + + counter = Counter() + count(root) + max_count = max(counter.values()) + return [val for val, count in counter.items() if count==max_count] + +""" +To use the feature of BST, we are going to inorder traverse the BST. +So it will be like we are iterating a sorted array. + +[1] +While iterating, we can put only the element count that is greater or equal than `max_count` to `ans`. +If we encounter a new element with larger `curr_count`, we reset the `ans`. + +[0] +With the help of `prev_val` we can know that `curr_node` is the same to the previous or not. +If not, its a new element, we need to reset the `curr_count`. + +Time: O(N). Space: O(LogN) + +For better understanding, below is a template for inorder traverse. +""" +class Solution(object): + def findMode(self, root): + if not root: return [] + + ans = [] + stack = [] + prev_val = None + curr = root + curr_count = 0 + max_count = float('-inf') + + while curr or stack: + while curr: + stack.append(curr) + curr = curr.left + curr = stack.pop() + + #[0] + if curr.val!=prev_val: + curr_count = 1 + prev_val = curr.val + else: + curr_count += 1 + + #[1] + if curr_count>max_count: + ans = [curr.val] + max_count = curr_count + elif curr_count==max_count: + ans.append(curr.val) + + curr = curr.right + + return ans + +#inorder traversal of BST +def inorder_traverse(root): + curr = root + stack = [] + while curr or stack: + while curr: + stack.append(curr) + curr = curr.left + curr = stack.pop() + + #do something to the current node + print curr.val + + curr = curr.right + + + + +""" +Time: O(N) +Space: O(N) +""" +class Solution(object): + def findMode(self, root): + def helper(node): + if not node: return + counter[node.val] += 1 + counter['maxCount'] = max(counter['maxCount'], counter[node.val]) + helper(node.left) + helper(node.right) + + ans = [] + counter = collections.Counter() + helper(root) + for v in counter: + if v!='maxCount' and counter[v]==counter['maxCount']: + ans.append(v) + + return ans \ No newline at end of file diff --git a/problems/python/find-original-array-from-doubled-array,py b/problems/python/find-original-array-from-doubled-array,py new file mode 100755 index 0000000..e9e2726 --- /dev/null +++ b/problems/python/find-original-array-from-doubled-array,py @@ -0,0 +1,26 @@ +""" +Time: O(NLogN) +Space: O(N) +""" +class Solution(object): + def findOriginalArray(self, changed): + if len(changed)%2!=0: return [] + + ans = [] + counter = collections.Counter() #store the count of the doubled number + count = 0 #sum of count in counter + + changed.sort() #need to be sorted, otherwise we cannot identify which number is orginal or it is doubled. + + for num in changed: + if counter[num]>0: + #num is a doubled num + counter[num] -= 1 + count -= 1 + ans.append(num/2) + else: + #num is an original num + counter[num*2] += 1 + count += 1 + + return ans if count==0 else [] \ No newline at end of file diff --git a/problems/find-peak-element.py b/problems/python/find-peak-element.py old mode 100644 new mode 100755 similarity index 78% rename from problems/find-peak-element.py rename to problems/python/find-peak-element.py index 5b9a8b3..aeb4726 --- a/problems/find-peak-element.py +++ b/problems/python/find-peak-element.py @@ -54,5 +54,24 @@ def findPeakElement(self, nums): return l - + +""" +[l, r] is the possible range. +Keep decreasing the range unsing binary search until l==r. +Pay attention to +m = l+(r-l+1)/2 +Sometimes you need m = l+(r-l)/2 to avoid infinite loop. +""" +class Solution(object): + def findPeakElement(self, nums): + l = 0 + r = len(nums)-1 + + while lN: + nums[i] = 1 + + for i in xrange(N): + a = abs(nums[i]) + + if a==N: + nums[0] = -abs(nums[0]) + else: + nums[a] = -abs(nums[a]) + + for i in xrange(1, N): + if nums[i]>0: return i + if nums[0]>0: return N + + return N+1 + +""" +Time: O(N), each num is going to be swap once. +Space: O(1). + +For nums with length N, the answer must be in between 1~N+1. + +[1] So for number between 1~N, lets put them in the right place. +number 1 will be stored at index 0. +number 2 will be stored at index 1. +... +number n will be stored at index n-1. + + +[2] Check if any number are not in place. + +[3] If all the number are in place, then the ans must be N+1. +""" +class Solution(object): + def firstMissingPositive(self, nums): + N = len(nums) + + #[1] + for i in xrange(N): + while 1<=nums[i]<=N and nums[i]!=i+1: + j = nums[i]-1 + if nums[i]==nums[j]: break #otherwise they are going to keep swapping infinitely, because one of them is not inplace. + nums[i], nums[j] = nums[j], nums[i] + + #[2] + for i in xrange(N): + if nums[i]!=i+1: return i+1 + + #[3] + return N+1 \ No newline at end of file diff --git a/problems/first-unique-character-in-a-string.py b/problems/python/first-unique-character-in-a-string.py old mode 100644 new mode 100755 similarity index 100% rename from problems/first-unique-character-in-a-string.py rename to problems/python/first-unique-character-in-a-string.py diff --git a/problems/fizz-buzz.py b/problems/python/fizz-buzz.py old mode 100644 new mode 100755 similarity index 100% rename from problems/fizz-buzz.py rename to problems/python/fizz-buzz.py diff --git a/problems/python/flatten-binary-tree-to-linked-list.py b/problems/python/flatten-binary-tree-to-linked-list.py new file mode 100755 index 0000000..630a144 --- /dev/null +++ b/problems/python/flatten-binary-tree-to-linked-list.py @@ -0,0 +1,47 @@ +""" +Time: O(N) +Space: O(N) + +Pre-Order Traversal +""" +class Solution(object): + def flatten(self, root): + if not root: return None + + preHead = TreeNode(0) + curr = preHead + stack = [root] + + while stack: + node = stack.pop() + + if node.right: stack.append(node.right) + if node.left: stack.append(node.left) + + node.right = None + node.left = None + curr.right = node + curr = curr.right + + return preHead.right + +""" +Time: O(N) +Space: O(1) + +Morris Traversal +""" +class Solution(object): + def flatten(self, root): + node = root + + while node: + if node.left: + rightmost = node.left + while rightmost.right: rightmost = rightmost.right + rightmost.right = node.right + node.right = node.left + node.left = None + else: + node = node.right + return root \ No newline at end of file diff --git a/problems/python/flip-binary-tree-to-match-preorder-traversal.py b/problems/python/flip-binary-tree-to-match-preorder-traversal.py new file mode 100755 index 0000000..8727e28 --- /dev/null +++ b/problems/python/flip-binary-tree-to-match-preorder-traversal.py @@ -0,0 +1,23 @@ +class Solution(object): + def flipMatchVoyage(self, root, voyage): + def helper(node): + if not node: return + if node.val!=voyage[self.i]: + self.valid = False + return + + self.i += 1 + if node.left and node.left.val!=voyage[self.i]: + self.ans.append(node.val) + helper(node.right) + helper(node.left) + + else: + helper(node.left) + helper(node.right) + + self.ans = [] + self.i = 0 + self.valid = True + helper(root) + return self.ans if self.valid else [-1] \ No newline at end of file diff --git a/problems/python/flip-string-to-monotone-increasing.py b/problems/python/flip-string-to-monotone-increasing.py new file mode 100755 index 0000000..ec2b0df --- /dev/null +++ b/problems/python/flip-string-to-monotone-increasing.py @@ -0,0 +1,20 @@ +""" +dp[i][0] := min number of flips to form monotone string that ends s[:i] at 0 +dp[i][1] := min number of flips to form monotone string that ends s[:i] at 1 + +Time: O(N) +Space: O(N), can further deduce to O(1) +""" +class Solution(object): + def minFlipsMonoIncr(self, s): + dp = [[0, 0] for _ in xrange(len(s)+1)] + + for i, c in enumerate(s): + if c=='0': + dp[i+1][0] = dp[i][0] + dp[i+1][1] = min(dp[i][0], dp[i][1]) + 1 + elif c=='1': + dp[i+1][0] = dp[i][0] + 1 + dp[i+1][1] = min(dp[i][0], dp[i][1]) + + return min(dp[-1]) \ No newline at end of file diff --git a/problems/python/flood-fill.py b/problems/python/flood-fill.py new file mode 100755 index 0000000..a18a13f --- /dev/null +++ b/problems/python/flood-fill.py @@ -0,0 +1,27 @@ +class Solution(object): + def floodFill(self, image, sr, sc, newColor): + stack = [(sr, sc)] + originColor = image[sr][sc] + + while stack: + i, j = stack.pop() + + if image[i][j]==newColor or image[i][j]!=originColor: continue + + image[i][j] = newColor + if i+12: + counter[fruits[i]] -= 1 + if counter[fruits[i]]==0: uniqueFruits -= 1 + i += 1 + + ans = max(ans, j-i+1) + return ans \ No newline at end of file diff --git a/problems/game-of-life.py b/problems/python/game-of-life.py old mode 100644 new mode 100755 similarity index 100% rename from problems/game-of-life.py rename to problems/python/game-of-life.py diff --git a/problems/generate-parentheses.py b/problems/python/generate-parentheses.py old mode 100644 new mode 100755 similarity index 53% rename from problems/generate-parentheses.py rename to problems/python/generate-parentheses.py index fb3ba3b..cadbe96 --- a/problems/generate-parentheses.py +++ b/problems/python/generate-parentheses.py @@ -20,3 +20,34 @@ def dfs(path, open_count, open_remain, close_remain): opt = [] dfs('', 0, N, N) return opt + + +class Solution(object): + def generateParenthesis(self, N): + def helper(open_remain, close_remain, s): + if open_remain==0 and close_remain==0: + ans.append(s) + if close_remain>open_remain and close_remain>0: + helper(open_remain, close_remain-1, s+')') + if open_remain>0: + helper(open_remain-1, close_remain, s+'(') + ans = [] + helper(N, N, '') + return ans + +""" +Time: O(2^N) +Space: O(N) +""" +class Solution(object): + def generateParenthesis(self, n): + def helper(curr, openCount, left, right): + if left==0 and right==0: ans.append(curr) + if left>0: + helper(curr+'(', openCount+1, left-1, right) + if right>0 and openCount>0: + helper(curr+')', openCount-1, left, right-1) + + ans = [] + helper('', 0, n, n) + return ans \ No newline at end of file diff --git a/problems/graph-valid-tree.py b/problems/python/graph-valid-tree.py old mode 100644 new mode 100755 similarity index 100% rename from problems/graph-valid-tree.py rename to problems/python/graph-valid-tree.py diff --git a/problems/python/greatest-sum-divisible-by-three.py b/problems/python/greatest-sum-divisible-by-three.py new file mode 100755 index 0000000..948e01b --- /dev/null +++ b/problems/python/greatest-sum-divisible-by-three.py @@ -0,0 +1,66 @@ +#DP +""" +Time: O(N) +Space: O(N). Can reduce to O(1). + +dp[i][0] max number n, which n%3==0, considering nums[0~i-1] +dp[i][1] max number n, which n%3==1, considering nums[0~i-1] +dp[i][2] max number n, which n%3==2, considering nums[0~i-1] +""" +class Solution(object): + def maxSumDivThree(self, nums): + dp = [[0]*3 for _ in xrange(len(nums)+1)] + + for i in xrange(len(nums)+1): + if i==0: + dp[i][0] = 0 + dp[i][1] = float('-inf') + dp[i][2] = float('-inf') + continue + + n = nums[i-1] + + if n%3==0: + dp[i][0] = max(dp[i-1][0], dp[i-1][0]+n) + dp[i][1] = max(dp[i-1][1], dp[i-1][1]+n) + dp[i][2] = max(dp[i-1][2], dp[i-1][2]+n) + elif n%3==1: + dp[i][0] = max(dp[i-1][0], dp[i-1][2]+n) + dp[i][1] = max(dp[i-1][1], dp[i-1][0]+n) + dp[i][2] = max(dp[i-1][2], dp[i-1][1]+n) + elif n%3==2: + dp[i][0] = max(dp[i-1][0], dp[i-1][1]+n) + dp[i][1] = max(dp[i-1][1], dp[i-1][2]+n) + dp[i][2] = max(dp[i-1][2], dp[i-1][0]+n) + + return dp[-1][0] + +# Greedy +class Solution(object): + def videoStitching(self, clips, T): + if T==0: return 0 + if not clips: return -1 + + clips.sort() + print clips + + if clips[0][0]!=0: return -1 + if clips[0][1]>=T: return 1 + + count = 0 + i = 0 + rightMost = 0 + + while i= T: return count + + return -1 \ No newline at end of file diff --git a/problems/group-anagrams.py b/problems/python/group-anagrams.py old mode 100644 new mode 100755 similarity index 51% rename from problems/group-anagrams.py rename to problems/python/group-anagrams.py index 26763fe..68b066e --- a/problems/group-anagrams.py +++ b/problems/python/group-anagrams.py @@ -14,3 +14,23 @@ def groupAnagrams(self, strs): for c in s: hashkey[ord(c)-97] +=1 anagrams[''.join(hashkey)].append(s) return anagrams.values() + +""" +Time: O(NK), N is the number of strings, K is the number of characters in the string. +Space: O(NK). +""" +class Solution(object): + def groupAnagrams(self, strs): + anagrams = collections.defaultdict(list) + + for s in strs: + anagrams[self.getKey(s)].append(s) + + return anagrams.values() + + def getKey(self, s): + key = '' + counts = collections.Counter(s) + for c in 'abcdefghijklmnopqrstuvwxyz': + key += counts[c]*c + return key \ No newline at end of file diff --git a/problems/python/group-shifted-strings.py b/problems/python/group-shifted-strings.py new file mode 100755 index 0000000..d5ac664 --- /dev/null +++ b/problems/python/group-shifted-strings.py @@ -0,0 +1,20 @@ +class Solution(object): + def groupStrings(self, strings): + def getHash(string): + h = '' + offset = getNumByChar(string[0])*-1 + for c in string: + h += getCharByNum((getNumByChar(c)+offset) if (getNumByChar(c)+offset)>=0 else 26+(getNumByChar(c)+offset)) + return h + + def getNumByChar(letter): + return ord(letter) - 97 + + def getCharByNum(pos): + return chr(pos + 97) + + groups = collections.defaultdict(list) + for string in strings: + h = getHash(string) + groups[h].append(string) + return groups.values() \ No newline at end of file diff --git a/problems/python/guess-number-higher-or-lower-ii.py b/problems/python/guess-number-higher-or-lower-ii.py new file mode 100755 index 0000000..6eac3d2 --- /dev/null +++ b/problems/python/guess-number-higher-or-lower-ii.py @@ -0,0 +1,18 @@ +""" +dp[i][j] := min money guarantee to win within i~j +k is the first guess, try all k and update dp[i][j]. +""" +class Solution(object): + def getMoneyAmount(self, N): + dp = [[float('inf') for _ in xrange(N)] for _ in xrange(N)] + for i in xrange(N): dp[i][i] = 0 + + + for l in xrange(2, N+1): + for i in xrange(N): + j = i+l-1 + if j>=N: continue + for k in xrange(i, j+1): + dp[i][j] = min(dp[i][j], max(dp[i][k-1] if k-1>=0 else 0, dp[k+1][j] if k+1=h: + #h may be the h-index, check larger h. + r = i + else: + #h is not h-index, check smaller h. + l = i+1 + + #now, l is equal to r + + return N-l if citations[l]!=0 else 0 #take care of edge case [0], [0, 0] or [0, 0, 0] \ No newline at end of file diff --git a/problems/python/h-index.py b/problems/python/h-index.py new file mode 100755 index 0000000..08ef891 --- /dev/null +++ b/problems/python/h-index.py @@ -0,0 +1,52 @@ +""" +First, reverse sort the list and iterate though it. +i = 0, if 1>=citations[i], it means that at least 1 of the citations is larger than 1. +i = 1, if 2>=citations[i], it means that at least 2 of the citations is larger than 2. +i = 3, if 3>=citations[i], it means that at least 3 of the citations is larger than 3. +... +... +Keep iterating until we fail the condition. + +Time: O(NLogN) +Space: O(1) +""" +class Solution(object): + def hIndex(self, citations): + citations.sort(reverse=True) + + ans = 0 + for i in xrange(len(citations)): + if i+1>citations[i]: break + ans = i+1 + + return ans + + +""" +First, count each citation and store it in counter. +The citation that is larger than N will be stored to N. [0] +Since for citations, the max possible h-index will be N (the length of the citations). +This can greatly reduce the index we go through when we iterate through counter. + +Second, since the max possible h-index is N, we start from N and iterate to 0. +Check if any of them is answer + +Time: O(N) +Space: O(N) +""" +import collections + +class Solution(object): + def hIndex(self, citations): + counter = collections.Counter() #count for each citation + N = len(citations) + count = 0 + + for citation in citations: + counter[min(N, citation)] += 1 #[0] + + for n in xrange(N, -1, -1): + count += counter[n] #count of citation that is larger or equal to n + if count>=n: return n + + return 0 \ No newline at end of file diff --git a/problems/hamming-distance.py b/problems/python/hamming-distance.py old mode 100644 new mode 100755 similarity index 100% rename from problems/hamming-distance.py rename to problems/python/hamming-distance.py diff --git a/problems/house-robber-ii.py b/problems/python/house-robber-ii.py old mode 100644 new mode 100755 similarity index 73% rename from problems/house-robber-ii.py rename to problems/python/house-robber-ii.py index 70c2086..5bdc6b3 --- a/problems/house-robber-ii.py +++ b/problems/python/house-robber-ii.py @@ -39,4 +39,23 @@ def rob(self, nums): else: K[i] = K[i-1]+nums[i] v2 = K[-1] - return max(v1, v2) \ No newline at end of file + return max(v1, v2) + +#2020/11/15 +class Solution(object): + def rob(self, nums): + if not nums: return 0 + if len(nums)==0 or len(nums)==1: return max(nums) + N = len(nums) + + v1 = nums[0] + v2 = nums[0] + for i in xrange(2, N-1): + v1, v2 = max(nums[i]+v2, v1), v1 + + w1 = nums[1] + w2 = 0 + for i in xrange(2, N): + w1, w2 = max(nums[i]+w2, w1), w1 + + return max(v1, w1) \ No newline at end of file diff --git a/problems/house-robber-iii.py b/problems/python/house-robber-iii.py old mode 100644 new mode 100755 similarity index 100% rename from problems/house-robber-iii.py rename to problems/python/house-robber-iii.py diff --git a/problems/house-robber.py b/problems/python/house-robber.py old mode 100644 new mode 100755 similarity index 61% rename from problems/house-robber.py rename to problems/python/house-robber.py index f3cb34b..f93f238 --- a/problems/house-robber.py +++ b/problems/python/house-robber.py @@ -26,4 +26,31 @@ def rob(self, nums): K[i] = max(K[i-2]+nums[i], K[i-1]) else: #[2] K[i] = K[i-1]+nums[i] - return K[-1] \ No newline at end of file + return K[-1] + +#2020/11/14 +class Solution(object): + def rob(self, nums): + def helper(i): + if i>=len(nums): return 0 + if i in history: return history[i] + history[i] = max(nums[i]+helper(i+2), helper(i+1)) + return history[i] + + history = {} + return helper(0) + +#2020/11/14 +class Solution(object): + def rob(self, nums): + if not nums: return 0 + if len(nums)==0 or len(nums)==1: return max(nums) + + last1 = max(nums[0], nums[1]) + last2 = nums[0] + + for i in xrange(len(nums)): + if i==0 or i==1: continue + last2, last1 = last1, max(nums[i]+last2, last1) + + return last1 \ No newline at end of file diff --git a/problems/python/implement-trie-prefix-tree.py b/problems/python/implement-trie-prefix-tree.py new file mode 100755 index 0000000..fd64dd7 --- /dev/null +++ b/problems/python/implement-trie-prefix-tree.py @@ -0,0 +1,46 @@ +""" +Time for insert() O(N), search() O(N), startsWith() O(N). N is the number of characters. +Space for insert() O(N), search() O(1), startsWith() O(1). N is the number of characters. + +Use . to represent the end of a string. +""" +class Node(object): + def __init__(self, char): + self.char = char + self.children = {} + +class Trie(object): + + def __init__(self): + self.period = '.' + self.root = Node('') + + + def insert(self, word): + word = word + self.period + node = self.root + + for c in word: + if c not in node.children: + node.children[c] = Node(c) + node = node.children[c] + + + def search(self, word): + word = word + self.period + node = self.root + + for c in word: + if c not in node.children: + return False + node = node.children[c] + return True + + + def startsWith(self, prefix): + node = self.root + for c in prefix: + if c not in node.children: + return False + node = node.children[c] + return True \ No newline at end of file diff --git a/problems/python/increasing-triplet-subsequence.py b/problems/python/increasing-triplet-subsequence.py new file mode 100755 index 0000000..60cecd7 --- /dev/null +++ b/problems/python/increasing-triplet-subsequence.py @@ -0,0 +1,24 @@ +""" +Time: O(N) +Space: O(1) + +Keep updating the minimum element (`min1`) and the second minimum element (`min2`). +When a new element comes up there are 3 possibilities. +0. Equals to min1 or min2 => do nothing. +1. Smaller than min1 => update min1. +2. Larger than min1 and smaller than min2 => update min2. +3. Larger than min2 => return True. +""" +class Solution(object): + def increasingTriplet(self, nums): + min1 = min2 = float('inf') + + for n in nums: + if n=tail.val or insertVal<=head.val: + tail.next = newNode + newNode.next = head + else: + curr = head + while not curr.val<=insertVal<=curr.next.val: curr = curr.next + temp = curr.next + curr.next = newNode + newNode.next = temp + + return random \ No newline at end of file diff --git a/problems/insertion-sort-list.py b/problems/python/insertion-sort-list.py old mode 100644 new mode 100755 similarity index 100% rename from problems/insertion-sort-list.py rename to problems/python/insertion-sort-list.py diff --git a/problems/python/integer-to-english-words.py b/problems/python/integer-to-english-words.py new file mode 100755 index 0000000..a4e8d88 --- /dev/null +++ b/problems/python/integer-to-english-words.py @@ -0,0 +1,28 @@ +class Solution(object): + def numberToWords(self, num): + underTwenty = ['', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen', ] + tens = ['', '', 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety'] + + def toWords(n): + if n==0: + return [] + elif n<20: + return [underTwenty[n]] + elif n<100: + c, r = n/10, n%10 + return [tens[c]] + toWords(r) + elif n<1000: + c, r = n/100, n%100 + return toWords(c) + ['Hundred'] + toWords(r) + else: + if n>=1000000000: + c, r = n/1000000000, n%1000000000 + return toWords(c) + ['Billion'] + toWords(r) + elif n>=1000000: + c, r = n/1000000, n%1000000 + return toWords(c) + ['Million'] + toWords(r) + elif n>=1000: + c, r = n/1000, n%1000 + return toWords(c) + ['Thousand'] + toWords(r) + + return ' '.join(toWords(num)) or 'Zero' \ No newline at end of file diff --git a/problems/python/interleaving-string.py b/problems/python/interleaving-string.py new file mode 100755 index 0000000..b389452 --- /dev/null +++ b/problems/python/interleaving-string.py @@ -0,0 +1,22 @@ +""" +dp[i][j] := whether s3[:i+j] is formed by an interleaving of s1[:i] and s2[:j] +""" +class Solution(object): + def isInterleave(self, s1, s2, s3): + if len(s1)+len(s2)!=len(s3): return False + + M, N = len(s1), len(s2) + dp = [[False for _ in xrange(N+1)] for _ in xrange(M+1)] + + for i in xrange(M+1): dp[i][0] = s1[:i] == s3[:i] + for j in xrange(N+1): dp[0][j] = s2[:j] == s3[:j] + + for i in xrange(1, M+1): + for j in xrange(1, N+1): + if s1[i-1]==s3[i+j-1] and dp[i-1][j]: + dp[i][j] = True + elif s2[j-1]==s3[i+j-1] and dp[i][j-1]: + dp[i][j] = True + + return dp[-1][-1] + \ No newline at end of file diff --git a/problems/python/intersection-of-two-arrays-ii.py b/problems/python/intersection-of-two-arrays-ii.py new file mode 100755 index 0000000..1ef39b6 --- /dev/null +++ b/problems/python/intersection-of-two-arrays-ii.py @@ -0,0 +1,43 @@ +""" +Time: O(NLogN) +Space: O(1) +""" +class Solution(object): + def intersect(self, nums1, nums2): + nums1.sort() + nums2.sort() + + i = j = 0 + ans = [] + + while inums2[j]: + j += 1 + else: + i += 1 + return ans + +""" +Time: O(N) +Space: O(N) +""" +class Solution(object): + def intersect(self, nums1, nums2): + c = {} #nums1 counter + ans = [] + + for n in nums1: + if n in c: + c[n] += 1 + else: + c[n] = 1 + + for n in nums2: + if n in c and c[n]>0: + ans.append(n) + c[n] -= 1 + return ans \ No newline at end of file diff --git a/problems/intersection-of-two-arrays.py b/problems/python/intersection-of-two-arrays.py old mode 100644 new mode 100755 similarity index 76% rename from problems/intersection-of-two-arrays.py rename to problems/python/intersection-of-two-arrays.py index 68bac01..585d8a3 --- a/problems/intersection-of-two-arrays.py +++ b/problems/python/intersection-of-two-arrays.py @@ -19,4 +19,12 @@ def intersection(self, nums1, nums2): #element in set is not repeated #set has some convenient build-in operator def intersection(self, nums1, nums2): - return list(set(nums1) & set(nums2)) \ No newline at end of file + return list(set(nums1) & set(nums2)) + + +class Solution(object): + def intersection(self, nums1, nums2): + nums1 = set(nums1) + nums2 = set(nums2) + + return nums1.intersection(nums2) \ No newline at end of file diff --git a/problems/python/invert-binary-tree.py b/problems/python/invert-binary-tree.py new file mode 100755 index 0000000..5dc3ec9 --- /dev/null +++ b/problems/python/invert-binary-tree.py @@ -0,0 +1,16 @@ +""" +Time: O(N) +Time: O(N) +""" +class Solution(object): + def invertTree(self, root): + if not root: return root + q = collections.deque([root]) + + while q: + node = q.popleft() + node.left, node.right = node.right, node.left + if node.left: q.append(node.left) + if node.right: q.append(node.right) + + return root \ No newline at end of file diff --git a/problems/python/is-graph-bipartite.py b/problems/python/is-graph-bipartite.py new file mode 100755 index 0000000..1ef6a4d --- /dev/null +++ b/problems/python/is-graph-bipartite.py @@ -0,0 +1,38 @@ +from collections import defaultdict + +class Solution(object): + def isBipartite(self, graph): + if not graph: return True + + A, B = set(), set() + stack = [] + + for node in xrange(len(graph)): #[1] + #check if visited, if not start DFS by putting it to stack + if node not in A and node not in B: + stack.append(node) + A.add(node) + + while stack: #[0] + n = stack.pop() + if n in A: + for nei in graph[n]: + if nei in A: return False + if nei not in B: + stack.append(nei) + B.add(nei) + elif n in B: + for nei in graph[n]: + if nei in B: return False + if nei not in A: + stack.append(nei) + A.add(nei) + return True +""" +Use DFS to travserse each node [0] +If the node is in A, all its children should be in B. +If the node is in B, all its children should be in A. + +Not necessary all the nodes in graph are connected. [1] +So we still need to loop the node to check if it is visited. +""" \ No newline at end of file diff --git a/problems/python/is-subsequence.py b/problems/python/is-subsequence.py new file mode 100755 index 0000000..9d283f5 --- /dev/null +++ b/problems/python/is-subsequence.py @@ -0,0 +1,9 @@ +class Solution(object): + def isSubsequence(self, s, t): + i = 0 + + for c in t: + if i>=len(s): break + if s[i]==c: i += 1 + + return i==len(s) \ No newline at end of file diff --git a/problems/python/isomorphic-strings.py b/problems/python/isomorphic-strings.py new file mode 100755 index 0000000..8785c2c --- /dev/null +++ b/problems/python/isomorphic-strings.py @@ -0,0 +1,33 @@ +""" +The description of the problem is unclear, let me rephrase it. + +Given two strings s and t, determine if they are isomorphic. +Two strings s and t are isomorphic if the characters in s can be replaced to get t and t can be replaced to get s. + +For each replacement, the characters in the same string must be replaced with another character while preserving the order of characters. +No two characters in the same string may map to the same character, but a character may map to itself. + +Two replacement are independent from each other. In other words, s -> t and t -> s does not affect each other. +""" + +""" +Time: O(N) +Space: O(N) +""" +class Solution(object): + def isIsomorphic(self, s, t): + if len(s)!=len(t): return False + + # check if s1 chars could be replaced and become s2 + def helper(s1, s2): + memo = {} + + for i in xrange(len(s)): + c1 = s1[i] + c2 = s2[i] + + if c1 in memo and memo[c1]!=c2: return False + memo[c1] = c2 + return True + + return helper(s, t) and helper(t, s) \ No newline at end of file diff --git a/problems/jewels-and-stones.py b/problems/python/jewels-and-stones.py old mode 100644 new mode 100755 similarity index 100% rename from problems/jewels-and-stones.py rename to problems/python/jewels-and-stones.py diff --git a/problems/jump-game.py b/problems/python/jump-game.py old mode 100644 new mode 100755 similarity index 100% rename from problems/jump-game.py rename to problems/python/jump-game.py diff --git a/problems/python/k-closest-points-to-origin.py b/problems/python/k-closest-points-to-origin.py new file mode 100755 index 0000000..1f536c1 --- /dev/null +++ b/problems/python/k-closest-points-to-origin.py @@ -0,0 +1,131 @@ +class Solution(object): + def kClosest(self, points, k): + h = [] + + for x, y in points: + d = (x**2+y**2)**0.5 + if len(h)>=k and -h[0][0]>d: + heapq.heappop(h) + heapq.heappush(h, (-d, x, y)) + elif len(h)>=k and -h[0][0]<=d: + pass + else: + heapq.heappush(h, (-d, x, y)) + + return [(x, y) for _, x, y in h] + + +""" +1. Process `points` into `distances`. + +2. Binary search the "distance". For every distance: +Split the elements in `distances` by distance +Put the ones smaller to smaller. +Put the ones larger to larger. +If len(smaller)<=k, then all the elements in the smaller must belong to the `ans`, do the same thing to the larger. +Else we ignore the larger and do the same thing to the smaller again. + +Time: O(N) +The binary search range in average is N, N/2, N/4... = 2N +Space: O(N) +""" +class Solution(object): + def kClosest(self, points, K): + #[1] + maxD = float('-inf') + minD = float('inf') + distances = [] + for x, y in points: + distance = ((x**2+y**2)**0.5) + distances.append((distance, x, y)) + maxD = max(maxD, distance) + minD = min(minD, distance) + + #[2] + ans = [] + smaller = [] + larger = [] + while K>0: + #split distances into smaller and larger + distance = (maxD+minD)/2 + for d, x, y in distances: + if d<=distance: + smaller.append((d, x, y)) + else: + larger.append((d, x, y)) + + if len(smaller)<=K: + ans += smaller + K -= len(smaller) + distances = larger + minD = distance + larger = [] + smaller = [] + else: + distances = smaller + maxD = distance + larger = [] + smaller = [] + + return [(x, y) for _, x, y in ans] + + + +""" +Quick Select +Time: O(N) +Space: O(N), can be optimize to O(1). +""" +class Solution(object): + def kClosest(self, points, K): + """ + Start State: + i = s #next element after "SSS"s + t = s #unprocessed elements + j = e #next element after "LLLL"s + + SSSPP?????LLL + i t j + + End State: + SSSPPPPPLLLLL + i jt + """ + def quickSelect(distances, s, e, k): + + pivot = distances[(s+e)/2][0] + i = s + j = e + t = s + + while t<=j: + if pivotdistances[t][0]: + distances[t], distances[i] = distances[i], distances[t] + i += 1 + t += 1 + else: + t += 1 + + if i-s>=k: + return quickSelect(distances, s, i, k) + elif j-s+1>=k: + return pivot + else: + return quickSelect(distances, t, e, k-(t-s)) + + distances = [] + for x, y in points: + distance = ((x**2+y**2)**0.5) + distances.append((distance, x, y)) + + kthSmallestDistance = quickSelect(distances, 0, len(distances)-1, K) + + ans = [] + for d, x, y in distances: + if len(ans)==K: break + if d<=kthSmallestDistance: ans.append((x, y)) + + return ans \ No newline at end of file diff --git a/problems/python/k-empty-slots.py b/problems/python/k-empty-slots.py new file mode 100755 index 0000000..0b4e36e --- /dev/null +++ b/problems/python/k-empty-slots.py @@ -0,0 +1,65 @@ +""" +Let's look at the naive approach first. + +Each day we turn on a bulb by `bulbs[i] = 1` and +1. `check()` the bulb on i to i+(K+1), see if it matches the pattern 1,0,0,..1 +2. `check()` the bulb on i to i-(K+1), see if it matches the pattern 1,0,0,..1 +The `check()` will take O(K), so the time complexity will be O(NK), N is the number of bulbs. + +Since iterating the bulbs seems inevitable, the only thing we can avoid is the iteration between l and r in `check()` which is taking O(K) of time. +""" +class Solution(object): + def kEmptySlots(self, schedule, K): + def check(l, r): + if l<0 or r>=len(bulbs): return False + if bulbs[l]!=1 or bulbs[r]!=1: return False + for k in xrange(l+1, r): + if bulbs[k]!=0: return False + return True + + bulbs = [0]*len(schedule) + for day, x in enumerate(schedule): + i = x-1 + bulbs[i] = 1 + if check(i, i+K+1): return day+1 + if check(i-(K+1), i): return day+1 + return -1 + + +""" +The reason we iterate between l and r is because we need to check there are only "0" between l and r, no "1". +This is where we can use SortedSet (also known as TreeSet in Java). +For each day, we also add "1" index to the SortedSet. +So instead of checking if it is all "0" between l and r, we check if there is any "1" between l and r. + +i = ss.bisect_right(l). The insetion point to a sorted list. If exist the same, insert to the right. +j = ss.bisect_left(r). The insetion point to a sorted list. If exist the same, insert to the left. + +j==0 means that r is the smallest index in the SortedSet. +i==len(ss) means that l is the largest index in the SortedSet. +i==j means that there is no other "1" between l and r. + +The time complexity is will be O(NLogN) +""" +from sortedcontainers import SortedSet + +class Solution(object): + def kEmptySlots(self, schedule, K): + def check(l, r): + if l<0 or r>=len(bulbs): return False + if bulbs[l]!=1 or bulbs[r]!=1: return False + + i = ss.bisect_right(l) + j = ss.bisect_left(r) + + return j!=0 and i!=len(ss) and i==j + + bulbs = [0]*len(schedule) + ss = SortedSet() + for day, x in enumerate(schedule): + i = x-1 + bulbs[i] = 1 + ss.add(i) + if check(i, i+K+1): return day+1 + if check(i-(K+1), i): return day+1 + return -1 \ No newline at end of file diff --git a/problems/python/keys-and-rooms.py b/problems/python/keys-and-rooms.py new file mode 100755 index 0000000..add404c --- /dev/null +++ b/problems/python/keys-and-rooms.py @@ -0,0 +1,20 @@ +class Solution(object): + def canVisitAllRooms(self, rooms): + if not rooms: return True + + visited = set() + stack = [0] + + while stack: + key = stack.pop() + if key in visited: continue + visited.add(key) + stack.extend(rooms[key]) + + return len(rooms)==len(visited) + +""" +Time: O(N). +Space: O(N). +N is the number of rooms. +""" \ No newline at end of file diff --git a/problems/python/knight-dialer.py b/problems/python/knight-dialer.py new file mode 100755 index 0000000..1342ed0 --- /dev/null +++ b/problems/python/knight-dialer.py @@ -0,0 +1,65 @@ +class Solution(object): + def knightDialer(self, n): + def helper(initial, n): + # return the number of posible count starting from initial with n steps left + + if str(initial)+'-'+str(n) in history: return history[str(initial)+'-'+str(n)] + count = 0 + + if n==0: return 1 + for next_number in memo[initial]: + count += helper(next_number, n-1) + + history[str(initial)+'-'+str(n)] = count + return count + + memo = { + 1: [6, 8], + 2: [7, 9], + 3: [4, 8], + 4: [0, 3, 9], + 5: [], + 6: [0, 1, 7], + 7: [2, 6], + 8: [1, 3], + 9: [2, 4], + 0: [4, 6] + } + + history = {} + count = 0 + + for i in xrange(10): + count += helper(i, n-1) + return count % 1000000007 + +""" +dp[n][i] := number of ways to ends at number i after n moves. + +Time: O(N). +Space: O(N). Can reduce to O(1). +""" +class Solution(object): + def knightDialer(self, n): + dp = [[0 for _ in xrange(10)] for _ in xrange(n)] + for i in xrange(10): dp[0][i] = 1 #initialize + + memo = { + 1: [6, 8], + 2: [7, 9], + 3: [4, 8], + 4: [0, 3, 9], + 5: [], + 6: [0, 1, 7], + 7: [2, 6], + 8: [1, 3], + 9: [2, 4], + 0: [4, 6] + } + + for j in xrange(n-1): + for i in xrange(10): + for next_n in memo[i]: + dp[j+1][next_n] += dp[j][i] + + return sum(dp[n-1]) % 1000000007 \ No newline at end of file diff --git a/problems/python/knight-probability-in-chessboard.py b/problems/python/knight-probability-in-chessboard.py new file mode 100755 index 0000000..427e3fb --- /dev/null +++ b/problems/python/knight-probability-in-chessboard.py @@ -0,0 +1,27 @@ +class Solution(object): + def knightProbability(self, N, K, r, c): + if K==0: return 1 + + dp = [[[0 for _ in xrange(N)] for _ in xrange(N)] for _ in xrange(K+1)] + + dp[0][r][c] = 1 + possible = float(0) + + for k in xrange(1, K+1): + for i in xrange(N): + for j in xrange(N): + if dp[k-1][i][j]>0: + for x, y in [(i+1, j+2), (i-1, j+2), (i+1, j-2), (i-1, j-2), (i+2, j+1), (i-2, j+1), (i+2, j-1), (i-2, j-1)]: + if 0<=x and x0: + ans = heapq.heappop(nums) + k -= 1 + + return ans + +""" +Quick Search + +Time: O(N) in average case, O(N^2) in worst case. +Spcae: O(1) +""" +class Solution(object): + def findKthLargest(self, nums, k): + def sortRange(A, l, r): + if l>=r: return A + + p = A[(l+r)/2] + i = partition(A, l, r, p) + if kp: r -= 1 + if l<=r: + A[l], A[r] = A[r], A[l] + l += 1 + r -= 1 + return l + + k = len(nums)-k #redefine the problem to find the kth nums when sorted + sortRange(nums, 0, len(nums)-1) + return nums[k] + + +#Quick Select +class Solution(object): + def findKthLargest(self, nums, K): + def quickSelect(A, s, e, K): + pivot = A[(s+e)/2] + i = s + t = s + j = e + + while t<=j: + if A[t]=K: + return quickSelect(A, j+1, e, K) + elif e-i+1>=K: + return pivot + else: + return quickSelect(A, s, i-1, K-(e-(i-1))) + + return quickSelect(nums, 0, len(nums)-1, K) \ No newline at end of file diff --git a/problems/python/kth-smallest-element-in-a-bst.py b/problems/python/kth-smallest-element-in-a-bst.py new file mode 100755 index 0000000..56c355b --- /dev/null +++ b/problems/python/kth-smallest-element-in-a-bst.py @@ -0,0 +1,89 @@ +class Solution(object): + def kthSmallest(self, root, k): + count = 0 + stack = [] + node = root + + while node or stack: + while node: + stack.append(node) + node = node.left + + node = stack.pop() + count += 1 + + if count==k: return node.val + + node = node.right + + return 0 + +""" +Time complexity: O(N). Because we use inorder traversal. +Space complexity: O(N). For stack may contains all the nodes. +""" + +""" +For follow up question. +We might need to keep a sorted list of node. +Every time we inset and delete. We need to update the list. Taking up O(N) of time. +(Originally it was O(LogN) for insert and delete) +But to find the kth smallest element will only takes O(1). +""" + + + + + + + + + + + + + + + +""" +Template for in-order traverse iteratively. +""" +def inOrderTraverse(root): + stack = [] + node = root + + while node or stack: + while node: + stack.append(node) + node = node.left + + node = stack.pop() + + #do something + print node.val + + node = node.right + +""" +Time complexity: O(N). Because we use inorder traversal. +Space complexity: O(N). For stack may contains all the nodes. + +Use the in-order traverse to find the kth smallest element. +""" +class Solution(object): + def kthSmallest(self, root, k): + stack = [] + node = root + + while node or stack: + while node: + stack.append(node) + node = node.left + + node = stack.pop() + + k -= 1 + if k==0: return node.val + + node = node.right + return node.val \ No newline at end of file diff --git a/problems/python/kth-smallest-element-in-a-sorted-matrix.py b/problems/python/kth-smallest-element-in-a-sorted-matrix.py new file mode 100755 index 0000000..5b015f1 --- /dev/null +++ b/problems/python/kth-smallest-element-in-a-sorted-matrix.py @@ -0,0 +1,49 @@ +class Solution(object): + def kthSmallest(self, matrix, k): + memo = [0]*len(matrix) + opt = [] + while len(opt)=k and dp[i+1][j+1-k+1][0]>=k: + ans = max(ans, k**2) + break + + elif grid[i][j]==0: + #[0] + dp[i+1][j+1][0] = 0 + dp[i+1][j+1][1] = 0 + + return ans + +""" +[0] +dp[i+1][j+1][0] := number of the vertical continuous `1`s above grid[i][j] +dp[i+1][j+1][0] := number of the vertical continuous `1`s left to grid[i][j] + +For each i and j + Imagine (i, j) is the bottom-right dot of the square. + [1] First, check the max length of the square: K. (through min(number of the vertical continuous `1`s above the bottom-right dot, number of the vertical continuous `1`s left to the bottom-right dot).) + [2] Then, for each possible k, checking from large to small + Get the number of the vertical continuous `1`s above the bottom-left dot. + Get the number of the vertical continuous `1`s left to the top-right dot. + See if they are large enough to form square with border==k. + +Time: O(N^3) assume that the N is the length of the grid and grid[0]. +Space: O(N^2) +""" \ No newline at end of file diff --git a/problems/python/largest-bst-subtree.py b/problems/python/largest-bst-subtree.py new file mode 100755 index 0000000..82afb93 --- /dev/null +++ b/problems/python/largest-bst-subtree.py @@ -0,0 +1,22 @@ +""" +Time: O(N) for recursively traverse each node once. +Space: O(LogN) for recursive stack. +""" +class Solution(object): + def largestBSTSubtree(self, root): + def helper(node, minVal, maxVal): + if not node: return True, 0, float('-inf'), float('inf') + if not node.left and not node.right: return True, 1, node.val, node.val + + isLeftBST, leftSize, leftMin, leftMax = helper(node.left, minVal, node.val) + isRightBST, rightSize, rightMin, rightMax = helper(node.right, node.val, maxVal) + + currMin = min(leftMin, rightMin, node.val) + currMax = max(leftMax, rightMax, node.val) + + if isLeftBST and isRightBST and leftMax=N: continue + avg[i][j] = float((avg[i+1][j-1]*((j-1)-(i+1)+1)+nums[i]+nums[j]))/(j-i+1) + + dp = [[0 for _ in xrange(K+1)] for _ in xrange(N+1)] + for i in xrange(1, N+1): dp[i][0] = float('-inf') + + for i in xrange(1, N+1): + for k in xrange(1, min(i, K)+1): + for j in xrange(k, i+1): + dp[i][k] = max(dp[i][k], dp[j-1][k-1] + avg[j-1][i-1]) + + return max(dp[N]) \ No newline at end of file diff --git a/problems/python/last-stone-weight-ii.py b/problems/python/last-stone-weight-ii.py new file mode 100755 index 0000000..c757f1f --- /dev/null +++ b/problems/python/last-stone-weight-ii.py @@ -0,0 +1,51 @@ +""" +dp[i][t] = considering stones[0~i-1], if it can sum up to target t + +Time: O(SN), S is the sum of stone weight. N is the number of stones. +Space: O(SN), can reduce to O(S). +""" +class Solution(object): + def lastStoneWeightII(self, stones): + total = sum(stones) + target = total/2 + dp = [[False for _ in xrange(target+1)] for _ in xrange(len(stones)+1)] + dp[0][0] = True + + maxSum = 0 + # Keep trace of the max sum that stones can sum up to. + + for i in xrange(1, len(stones)+1): + for t in xrange(target+1): + if (dp[i-1][t] or (t-stones[i-1]>=0 and dp[i-1][t-stones[i-1]])): + # it can sum up to t considering stones[0~i-2] + # OR + # it can sum up to t considering stones[0~i-1] + dp[i][t] = True + maxSum = max(maxSum, t) + if t==target: return total-maxSum*2 + + # Two collection of stones will be total-maxSum and maxSum + # (total-maxSum) - maxSum => total-maxSum*2 + return total-maxSum*2 + + +""" +dp[i][w] := if weight w is achievable using stones[:i] +Find the smallest achivable w where w>=0. +""" +import collections +class Solution(object): + def lastStoneWeightII(self, stones): + N = len(stones) + W = sum(stones) + dp = [collections.defaultdict(bool) for _ in xrange(N+1)] + dp[0][0] = True + + for i in xrange(1, N+1): + for w in xrange(-W, W+1): + dp[i][w] = dp[i-1][w+stones[i-1]] or dp[i-1][w-stones[i-1]] + + for w, b in dp[N].iteritems(): + if b and w>=0: return w + + return 0 \ No newline at end of file diff --git a/problems/python/last-stone-weight.py b/problems/python/last-stone-weight.py new file mode 100755 index 0000000..dcfaadc --- /dev/null +++ b/problems/python/last-stone-weight.py @@ -0,0 +1,26 @@ +import bisect +class Solution(object): + def lastStoneWeight(self, stones): + stones.sort() + + while len(stones)>1: + x = stones.pop() + y = stones.pop() + bisect.insort_left(stones, abs(x-y)) + + return 0 if not stones else stones[0] + +import heapq +class Solution(object): + def lastStoneWeight(self, stones): + h = [] + + for stone in stones: + heapq.heappush(h, stone*-1) + + while len(h)>1: + x = heapq.heappop(h) + y = heapq.heappop(h) + heapq.heappush(h, abs(x-y)*-1) + + return 0 if not h else abs(h[0]) \ No newline at end of file diff --git a/problems/python/least-number-of-unique-integers-after-k-removals.py b/problems/python/least-number-of-unique-integers-after-k-removals.py new file mode 100755 index 0000000..ec69576 --- /dev/null +++ b/problems/python/least-number-of-unique-integers-after-k-removals.py @@ -0,0 +1,20 @@ +""" +Time: Build counter takes O(N). Build heap takes O(NLogN). Last operation takes O(KLogN). +Since N>k, O(N+NLogN+KLogN) ~= O(NLogN) + +Space: O(N) +""" +class Solution(object): + def findLeastNumOfUniqueInts(self, nums, k): + counter = collections.Counter(nums) + h = [] + + for num in counter: + heapq.heappush(h, (counter[num], num)) + + for _ in xrange(k): + count, num = heapq.heappop(h) + count -= 1 + if count>0: heapq.heappush(h, (count, num)) + + return len(h) \ No newline at end of file diff --git a/problems/letter-case-permutation.py b/problems/python/letter-case-permutation.py old mode 100644 new mode 100755 similarity index 100% rename from problems/letter-case-permutation.py rename to problems/python/letter-case-permutation.py diff --git a/problems/letter-combinations-of-a-phone-number.py b/problems/python/letter-combinations-of-a-phone-number.py old mode 100644 new mode 100755 similarity index 100% rename from problems/letter-combinations-of-a-phone-number.py rename to problems/python/letter-combinations-of-a-phone-number.py diff --git a/problems/license-key-formatting.py b/problems/python/license-key-formatting.py old mode 100644 new mode 100755 similarity index 100% rename from problems/license-key-formatting.py rename to problems/python/license-key-formatting.py diff --git a/problems/linked-list-cycle-ii.py b/problems/python/linked-list-cycle-ii.py old mode 100644 new mode 100755 similarity index 100% rename from problems/linked-list-cycle-ii.py rename to problems/python/linked-list-cycle-ii.py diff --git a/problems/linked-list-cycle.py b/problems/python/linked-list-cycle.py old mode 100644 new mode 100755 similarity index 100% rename from problems/linked-list-cycle.py rename to problems/python/linked-list-cycle.py diff --git a/problems/python/linked-list-random-node.py b/problems/python/linked-list-random-node.py new file mode 100755 index 0000000..1c9bcfb --- /dev/null +++ b/problems/python/linked-list-random-node.py @@ -0,0 +1,22 @@ +""" +Time: O(N) for __init__() and getRandom() +Space: O(1) +""" +class Solution(object): + + def __init__(self, head): + self.count = 1 + self.curr = head + + while self.curr.next: + self.curr = self.curr.next + self.count += 1 + self.curr.next = head + + + def getRandom(self): + rand = randrange(self.count) + while rand>0: + rand -= 1 + self.curr = self.curr.next + return self.curr.val \ No newline at end of file diff --git a/problems/python/logger-rate-limiter.py b/problems/python/logger-rate-limiter.py new file mode 100755 index 0000000..0f5f7ad --- /dev/null +++ b/problems/python/logger-rate-limiter.py @@ -0,0 +1,32 @@ +class Logger(object): + def __init__(self): + self.log = collections.Counter() #store the latest timestamp + + def shouldPrintMessage(self, timestamp, message): + if message not in self.log or self.log[message]+10<=timestamp: + self.log[message] = timestamp + return True + else: + return False + + + +class Logger(object): + + def __init__(self): + #stores the messages within 10 seconds + self.q = collections.deque() + self.set = set() + + + def shouldPrintMessage(self, timestamp, message): + while self.q and timestamp-self.q[0][0]>=10: + time, msg = self.q.popleft() + self.set.remove(msg) + + if message not in self.set: + self.q.append((timestamp, message)) + self.set.add(message) + return True + else: + return False \ No newline at end of file diff --git a/problems/longest-common-prefix.py b/problems/python/longest-common-prefix.py old mode 100644 new mode 100755 similarity index 63% rename from problems/longest-common-prefix.py rename to problems/python/longest-common-prefix.py index 083357e..b3d2e83 --- a/problems/longest-common-prefix.py +++ b/problems/python/longest-common-prefix.py @@ -21,4 +21,20 @@ def longestCommonPrefix(self, strs): for s in strs: if s[:i]!=common_substring: #[2] return bench_mark[:i-1] - return bench_mark #[3] \ No newline at end of file + return bench_mark #[3] + +# 2021/7/10 +class Solution(object): + def longestCommonPrefix(self, strs): + j = 0 + minLen = float('inf') + for s in strs: minLen = min(minLen, len(s)) + if minLen==float('inf'): return "" + + while j=0 and matrix[i][j]=0 and matrix[i][j]nums[j]: + dp[i] = max(dp[i], dp[j]+1) + + return max(dp) +""" +dp[i] := longest increasing subsequence that ends at nums[i] +dp[i] = max{ dp[j] where j = 0~i-1 } + 1 +""" \ No newline at end of file diff --git a/problems/python/longest-palindromic-subsequence.py b/problems/python/longest-palindromic-subsequence.py new file mode 100755 index 0000000..2ec907e --- /dev/null +++ b/problems/python/longest-palindromic-subsequence.py @@ -0,0 +1,17 @@ +""" +dp[i][j] := longest palindromic subsequence of s[i:j+1] +""" +class Solution(object): + def longestPalindromeSubseq(self, s): + N = len(s) + + dp = [[0 for _ in xrange(N)] for _ in xrange(N)] + for i in xrange(N): dp[i][i] = 1 + + for l in xrange(2, N+1): + for i in xrange(N): + j = i+l-1 + if j>=N: continue + dp[i][j] = dp[i+1][j-1]+2 if s[i]==s[j] else max(dp[i+1][j], dp[i][j-1]) + + return dp[0][N-1] \ No newline at end of file diff --git a/problems/longest-palindromic-substring.py b/problems/python/longest-palindromic-substring.py old mode 100644 new mode 100755 similarity index 69% rename from problems/longest-palindromic-substring.py rename to problems/python/longest-palindromic-substring.py index 9b916f1..93b76a1 --- a/problems/longest-palindromic-substring.py +++ b/problems/python/longest-palindromic-substring.py @@ -41,4 +41,26 @@ def findPalindorome(mid, mid2=None): max_pal = p2 if len(p2)>len(max_pal) else max_pal return max_pal - \ No newline at end of file + + +#2021/6/18 DP TLE +""" +dp[i][j] := if s[i:j+1] is palindrom +""" +class Solution(object): + def longestPalindrome(self, s): + if not s: return s + + N = len(s) + dp = [[False for _ in xrange(N+1)] for _ in xrange(N+1)] + for i in xrange(N+1): dp[i][i] = True + ans = s[0] + + for l in xrange(2, N+1): + for i in xrange(1, N+1): + j = i+l-1 + if j>N: continue + dp[i][j] = s[i-1]==s[j-1] and (dp[i+1][j-1] or j-1k: + counter[s[l]] -= 1 + l += 1 + + ans = max(ans, r-l+1) + return ans \ No newline at end of file diff --git a/problems/python/longest-string-chain.py b/problems/python/longest-string-chain.py new file mode 100755 index 0000000..a21ef17 --- /dev/null +++ b/problems/python/longest-string-chain.py @@ -0,0 +1,107 @@ +import collections + +""" +DP + +Time: O(NLogN)+O(NSS), + +Where N is the number of words and S is the length of each word. (S<=16) +NLogN is for sorting words. NSS is for the double for-loop. + +Space: O(NS) +""" +class Solution(object): + def longestStrChain(self, words): + dp = {} + + for word in sorted(words, key=len): + dp[word] = 1 + for i in xrange(len(word)): + posible_predecessor = word[:i]+word[i+1:] + dp[word] = max(dp.get(word), dp.get(posible_predecessor, 0)+1) + + return max(dp.values()) + +""" +Recursive + +Time: O(N^2 x SS), +Basically for every word, check if there are predecessor exist in the words. +If so, keep looking. Until we find the smallest predecessor. +S is the length of each word. isPredecessor takes SS. + +Space: O(NS) +""" +class Solution(object): + def longestStrChain(self, words): + def isPredecessor(word, word2): + if not word or not word2: return False + for i in xrange(len(word)): + if word[:i]+word[i+1:]==word2: + return True + return False + + def shortestPredecessor(word): + if word in history: return history[word] + shortest_predecessor = word + for word2 in word_group[len(word)-1]: + if isPredecessor(word, word2): + sp = shortestPredecessor(word2) + if len(sp)=k, the answer must be in `s[:i]` or `s[i+1:]`. +""" +import collections + +class Solution(object): + def longestSubstring(self, s, k): + if not s: return 0 + counter = collections.Counter(s) + + i = 0 + while i=k: + i += 1 + else: + return max(self.longestSubstring(s[:i], k), self.longestSubstring(s[i+1:], k)) + return i + +""" +Optimize the above solution to using O(1) space by pointers `l` and `r` +""" +class Solution(object): + def longestSubstring(self, s, k): + def helper(l, r): + if r-l==0: return 0 + counter = collections.Counter(s[l:r]) + + i = l + while i=k: + i += 1 + else: + return max(helper(l, i), helper(i+1, r)) + return i-l + + return helper(0, len(s)) + +""" +Time: O(N). Each `helper()` takes O(N), uniqueCount will be at most 26, O(26N) ~= O(N). +Space: O(N). + +Use a sliding window (`s[i:j+1]`) to go through `s`. +Givin a fixed i we move the j as right as possible. Then move the i right (Before we move the i, recalculate a those var tracking s[i:j+1]) [0] +For each sliding window if all the unique char has count>=k (`len(counter)==m and count==m`), update the answer. + +What is "m"? +Without "m", you will soon notice that we are not able to stop sliding window growing. +So we need to add the constraint: Get longestSubstring with m unique char. [1] +The final answer will be the max of: +longestSubstring with 1 unique char. +longestSubstring with 2 unique char. +... +... +longestSubstring with uniqueCount unique char. +""" +class Solution(object): + def longestSubstring(self, s, k): + ans = 0 + uniqueCount = len(set(s)) + + for m in xrange(1, uniqueCount+1): + ans = max(ans, self.helper(s, k, m)) + + return ans + + #[1] + def helper(self, s, k, m): + j = 0 + counter = collections.Counter() #count each char in s[i:j+1] + countOverK = 0 #number of char in s[i:j+1] count>=k + ans = 0 + + for i in xrange(len(s)): + while jq.val and node.val>p.val: + node = node.left + elif node.val=2 and not self.ans: self.ans = node + return count + + dfs(root) + return self.ans diff --git a/problems/python/lowest-common-ancestor-of-a-binary-tree-iii.py b/problems/python/lowest-common-ancestor-of-a-binary-tree-iii.py new file mode 100755 index 0000000..cfd14f7 --- /dev/null +++ b/problems/python/lowest-common-ancestor-of-a-binary-tree-iii.py @@ -0,0 +1,56 @@ +""" +Time: O(H), H is the height of the tree. +Space: O(1) +""" +class Solution(object): + def lowestCommonAncestor(self, p, q): + ancestorP = set() + ancestorQ = set() + + temp = p + while temp: + ancestorP.add(temp) + temp = temp.parent + + temp = q + while temp: + ancestorQ.add(temp) + temp = temp.parent + + commonAncestor = ancestorQ.intersection(ancestorP) + temp = q + while temp: + if temp in commonAncestor: return temp + temp = temp.parent + return None + + +""" +Time: O(LogN) +Space: O(LogN) + +Looking from backward, parents1 and parents2 will be the same at first, since they must have a common ancestor. +Find the last the same parents. +""" +class Solution(object): + def lowestCommonAncestor(self, p, q): + parents1 = [] + parents2 = [] + + curr = p + while curr: + parents1.append(curr) + curr = curr.parent + + curr = q + while curr: + parents2.append(curr) + curr = curr.parent + + i = len(parents1)-1 + j = len(parents2)-1 + while i>=0 and j>=0 and parents1[i]==parents2[j]: + i -= 1 + j -= 1 + return parents1[i+1] + diff --git a/problems/python/lowest-common-ancestor-of-a-binary-tree-iv.py b/problems/python/lowest-common-ancestor-of-a-binary-tree-iv.py new file mode 100755 index 0000000..f7458a3 --- /dev/null +++ b/problems/python/lowest-common-ancestor-of-a-binary-tree-iv.py @@ -0,0 +1,18 @@ +class Solution(object): + def __init__(self): + self.ans = None + + def lowestCommonAncestor(self, root, nodes): + def dfs(node): + if not node: return 0 + + count = 0 + if node in nodes: count += 1 + count += dfs(node.left) + count += dfs(node.right) + if count>=len(nodes) and not self.ans: self.ans = node + return count + + nodes = set(nodes) + dfs(root) + return self.ans \ No newline at end of file diff --git a/problems/lowest-common-ancestor-of-a-binary-tree.py b/problems/python/lowest-common-ancestor-of-a-binary-tree.py old mode 100644 new mode 100755 similarity index 88% rename from problems/lowest-common-ancestor-of-a-binary-tree.py rename to problems/python/lowest-common-ancestor-of-a-binary-tree.py index fdbabd3..54546a3 --- a/problems/lowest-common-ancestor-of-a-binary-tree.py +++ b/problems/python/lowest-common-ancestor-of-a-binary-tree.py @@ -129,4 +129,25 @@ def find_lowest_common(a1, a2): find_ancestors() is O(LogN). find_lowest_common is O(LogN). Space complexity is O(N), since `genealogy` may carry all the nodes. -""" \ No newline at end of file +""" + + +# 2021/9/25 +class Solution(object): + def lowestCommonAncestor(self, root, p, q): + def __init__(self): + self.ans = None + + def helper(node): + + if not node: return False + mid = node==q or node==p + left = helper(node.left) + right = helper(node.right) + + if (mid and left) or (mid and right) or (left and right): self.ans = node + + return mid or left or right + + helper(root) + return self.ans \ No newline at end of file diff --git a/problems/python/lowest-common-ancestor-of-deepest-leaves.py b/problems/python/lowest-common-ancestor-of-deepest-leaves.py new file mode 100755 index 0000000..cffade3 --- /dev/null +++ b/problems/python/lowest-common-ancestor-of-deepest-leaves.py @@ -0,0 +1,41 @@ +class Solution(object): + def __init__(self): + self.ans = None + + def lcaDeepestLeaves(self, root): + def checkCount(node, deepestNode): + if not node: + count = 0 + if count==len(deepestNode) and not self.ans: self.ans = node + return 0 + + if node in deepestNode: + count = 1 + if count==len(deepestNode) and not self.ans: self.ans = node + return count + + leftCount = checkCount(node.left, deepestNode) + rightCount = checkCount(node.right, deepestNode) + + if leftCount+rightCount==len(deepestNode) and not self.ans: self.ans = node + return leftCount+rightCount + + q = collections.deque([(root, 0)]) + q2 = collections.deque() + deepestNode = set([node for node, h in q]) + + while q: + node, d = q.popleft() + if node.left: q2.append((node.left, d+1)) + if node.right: q2.append((node.right, d+1)) + if not q: + q = q2 + if q: deepestNode = set([node for node, h in q]) + q2 = collections.deque() + + checkCount(root, deepestNode) + return self.ans + + + + \ No newline at end of file diff --git a/problems/lru-cache.py b/problems/python/lru-cache.py old mode 100644 new mode 100755 similarity index 100% rename from problems/lru-cache.py rename to problems/python/lru-cache.py diff --git a/problems/python/majority-element-ii.py b/problems/python/majority-element-ii.py new file mode 100755 index 0000000..02f3252 --- /dev/null +++ b/problems/python/majority-element-ii.py @@ -0,0 +1,14 @@ +class Solution(object): + def majorityElement(self, nums): + ans = [] + counter = {} + requiredCount = len(nums)/3.0 + + for n in nums: + if n not in counter: counter[n] = 0 + counter[n] += 1 + + for n in counter: + if counter[n]>requiredCount: ans.append(n) + + return ans \ No newline at end of file diff --git a/problems/python/majority-element.py b/problems/python/majority-element.py new file mode 100755 index 0000000..dead6e3 --- /dev/null +++ b/problems/python/majority-element.py @@ -0,0 +1,38 @@ +""" +Time: O(N) +Space: O(N) +""" +class Solution(object): + def majorityElement(self, nums): + counter = {} + + for n in nums: + if n not in counter: counter[n] = 0 + counter[n] += 1 + if counter[n]>len(nums)/2.0: return n + + return 0 + +""" +Time: O(N) +Space: O(1) + +Boyer-Moore +If a number `n` is not majority number its `count` will certainly become 0 and `ans` will switch to another number `n`. +If a number `n` is majority number its `count` will certainly larger thans 0 at the end, since it is the "majority". +""" +class Solution(object): + def majorityElement(self, nums): + ans = nums[0] + count = 0 + + for n in nums: + if n==ans: + count += 1 + else: + count -= 1 + if count==0: + ans = n + count = 1 + return ans + \ No newline at end of file diff --git a/problems/python/making-a-large-island.py b/problems/python/making-a-large-island.py new file mode 100755 index 0000000..215aa2a --- /dev/null +++ b/problems/python/making-a-large-island.py @@ -0,0 +1,51 @@ +""" +For each "island" asign them a group id. Also calculate the group's size. +Iterate all the zeros, update the ans. + +Time:O(MN) +Space: O(MN) in the worst case. +""" +class Solution(object): + def largestIsland(self, grid): + def isValid(i, j, M, N): + return 0<=i1: + neiGroupId.add(grid[iNext][jNext]) + + for groupId in list(neiGroupId): + neiSize += groupIdToSize[groupId] + + ans = max(ans, 1+neiSize) + + return ans \ No newline at end of file diff --git a/problems/python/max-area-of-island.py b/problems/python/max-area-of-island.py new file mode 100755 index 0000000..82acf5b --- /dev/null +++ b/problems/python/max-area-of-island.py @@ -0,0 +1,44 @@ +class Solution(object): + def maxAreaOfIsland(self, grid): + def dfs(i0, j0): + stack = [(i0, j0)] + area = 0 + + while stack: + i, j = stack.pop() + if grid[i][j]==0 or grid[i][j]==2: continue + grid[i][j] = 2 + area += 1 + + if i+1k: + if nums[i]==0: zeroCount -= 1 + i += 1 + ans = max(ans, j-i+1) + + return ans \ No newline at end of file diff --git a/problems/max-stack.py b/problems/python/max-stack.py old mode 100644 new mode 100755 similarity index 100% rename from problems/max-stack.py rename to problems/python/max-stack.py diff --git a/problems/python/max-sum-of-rectangle-no-larger-than-k.py b/problems/python/max-sum-of-rectangle-no-larger-than-k.py new file mode 100755 index 0000000..eed5131 --- /dev/null +++ b/problems/python/max-sum-of-rectangle-no-larger-than-k.py @@ -0,0 +1,52 @@ +""" +Time: O(min(M,N)^2 * max(M,N) * Log(max(M,N))) +Space: O(MN), can reduce to O(max(M,N)) by not using a new 2D array "matrixRotated". +Just change the iteration in `maxSumSubmatrix()`. + +This is the implementation of the offical solution 1 and 2. +""" +from sortedcontainers import SortedSet + +class Solution(object): + def maxSumSubmatrix(self, matrix, k): + if len(matrix)>len(matrix[0]): matrix = self.rotate(matrix) + + ans = float('-inf') + M = len(matrix) + N = len(matrix[0]) + + for start in xrange(M): + rowSum = [0]*N #row sum of rows from matrix[start] to row + for row in matrix[start:]: + for i, n in enumerate(row): rowSum[i] += n + ans = max(ans, self.maxSumRow(rowSum, k)) + if ans==k: return ans + return ans + + def maxSumRow(self, row, k): + ans = float('-inf') + total = 0 + + ss = SortedSet() + ss.add(0) + + for n in row: + total += n + i = ss.bisect_left(total-k) + if i O(dN). +Since the number of the digit is fixed in range 10 (stated in the problem) => O(dN) ~= O(N). + +Space: O(N) +""" +class Solution(object): + def maximumGap(self, nums): + if len(nums)<2: return 0 + + self.radixSort(nums) + + ans = 0 + for i in xrange(1, len(nums)): + ans = max(ans, abs(nums[i]-nums[i-1])) + return ans + + def radixSort(self, nums): + maxNumberOfDigits = len(str(max(nums))) + + for d in xrange(maxNumberOfDigits): + b = [[] for _ in xrange(10)] + + for num in nums: + n = (num//10**d)%10 + print num, d, n + b[n].append(num) + + i = 0 + for a in b: + for num in a: + nums[i] = num + i += 1 \ No newline at end of file diff --git a/problems/python/maximum-length-of-repeated-subarray.py b/problems/python/maximum-length-of-repeated-subarray.py new file mode 100755 index 0000000..dc099ec --- /dev/null +++ b/problems/python/maximum-length-of-repeated-subarray.py @@ -0,0 +1,13 @@ +class Solution(object): + def findLength(self, A, B): + M, N = len(A), len(B) + + #dp[i][j] := the logest length of sub array that needs to involve A[i] and B[j] + dp = [[0 for _ in xrange(N+1)] for _ in xrange(M+1)] + + for i in xrange(1, M+1): + for j in xrange(1, N+1): + if A[i-1]==B[j-1]: + dp[i][j] = dp[i-1][j-1]+1 + + return max(max(row) for row in dp) \ No newline at end of file diff --git a/problems/python/maximum-number-of-events-that-can-be-attended.py b/problems/python/maximum-number-of-events-that-can-be-attended.py new file mode 100755 index 0000000..087489d --- /dev/null +++ b/problems/python/maximum-number-of-events-that-can-be-attended.py @@ -0,0 +1,25 @@ +""" +Time: O(NLogN). Sort the event takes O(NLogN). Each event will get push in and pop out the heap: O(NLogN) +Space: O(N) +""" +class Solution(object): + def maxEvents(self, A): + d = 0 + count = 0 + h = [] #a heap. store the started event + A.sort(reverse=True) + + #for each day, attend the event with smallest endtime, so we can have the most free time in the future. + while A or h: + if not h: d = A[-1][0] + + while A and A[-1][0]<=d: + heapq.heappush(h, A.pop()[1]) + + heapq.heappop(h) #attend the event with smallest endtime + count += 1 + d += 1 + + while h and h[0]=k (Part 1) +points[i][j]+max([dp[i-1][k] - (j-k) for k in xrange(M)]) +points[i][j] - j + max([dp[i-1][k] + k) for k in xrange(M)]) + +if k>=j (Part 2) +points[i][j] + max([dp[i-1][k] - (k-j) for k in xrange(M)]) +points[i][j] + j + max([dp[i-1][k] - k) for k in xrange(M)]) + +Since we cannot do a full scan +why not we update the value from left to right for Part 1 and +right to left for part 2 +With a variable call rollingMax to store the max. + +That way dp[i][j] will be updated as if we do a full scan. + +The time complexity will become O(NM) +""" +class Solution(object): + def maxPoints(self, points): + N = len(points) + M = len(points[0]) + dp = [[0]*M for _ in xrange(N)] + + for j in xrange(M): + dp[0][j] = points[0][j] + + for i in xrange(1, N): + rollingMax = float('-inf') + for j in xrange(M): + rollingMax = max(rollingMax, dp[i-1][j] - j) + dp[i][j] = max(dp[i][j], points[i][j] + j + rollingMax)) + + rollingMax = float('-inf') + for j in xrange(M, -1, -1): + rollingMax = max(rollingMax, dp[i-1][j] + j) + dp[i][j] = max(dp[i][j], points[i][j] - j + rollingMax)) + + return max(dp[N-1]) \ No newline at end of file diff --git a/problems/python/maximum-number-of-visible-points.py b/problems/python/maximum-number-of-visible-points.py new file mode 100755 index 0000000..f247156 --- /dev/null +++ b/problems/python/maximum-number-of-visible-points.py @@ -0,0 +1,54 @@ +""" +Time: O(NLogN), N is the number of points. +Space: O(N) + +1. Get the angle of each point relative to the "location" +2. Sort the angles +3. Do a sliding window to angles to see what the maximum number of angles within the interval "angle" + +[1] For example, angles = [10, 20, 360], angle = 20 we will count 2, but actually it will be 3. +""" +class Solution(object): + def visiblePoints(self, points, angle, location): + def getAngle(x, y): + #4 axis + if x>0 and y==0: + return 0 + elif x==0 and y>0: + return 90 + elif x<0 and y==0: + return 180 + elif x==0 and y<0: + return 270 + + #4 quadrant + if x>0 and y>0: + return math.degrees(math.atan2(abs(y), abs(x))) + elif x<0 and y>0: + return 180-math.degrees(math.atan2(abs(y), abs(x))) + elif x<0 and y<0: + return 180+math.degrees(math.atan2(abs(y), abs(x))) + else: + return 360-math.degrees(math.atan2(abs(y), abs(x))) + + ans = 0 + onLocation = 0 + angles = [] + + for x, y in points: + if x==location[0] and y==location[1]: + onLocation += 1 + else: + a = getAngle(x-location[0], y-location[1]) + angles.append(a) + if a<=angle: angles.append(360+a) #[1] + + angles.sort() + + i = 0 + for j in xrange(len(angles)): + while angles[j]-angles[i]>angle: + i += 1 + ans = max(ans, j-i+1) + + return ans+onLocation \ No newline at end of file diff --git a/problems/maximum-product-of-three-numbers.py b/problems/python/maximum-product-of-three-numbers.py old mode 100644 new mode 100755 similarity index 100% rename from problems/maximum-product-of-three-numbers.py rename to problems/python/maximum-product-of-three-numbers.py diff --git a/problems/python/maximum-product-subarray.py b/problems/python/maximum-product-subarray.py new file mode 100755 index 0000000..b48bf12 --- /dev/null +++ b/problems/python/maximum-product-subarray.py @@ -0,0 +1,57 @@ +""" +Time: O(N) +Space: O(N) + +dp[i][0] := the maximum subarray product that includes nums[i] +dp[i][1] := the minimum subarray product that includes nums[i] +""" +class Solution(object): + def maxProduct(self, nums): + if not nums: return 0 + dp = [[float('-inf'), float('inf')] for _ in xrange(len(nums))] + + dp[0] = [nums[0], nums[0]] + ans = nums[0] + + for i in xrange(1, len(nums)): + if nums[i]==0: + dp[i][0] = 0 + dp[i][1] = 0 + elif nums[i]>0: + dp[i][0] = dp[i-1][0]*nums[i] if dp[i-1][0]>0 else nums[i] + dp[i][1] = dp[i-1][1]*nums[i] if dp[i-1][1]<=0 else nums[i] + else: + dp[i][0] = dp[i-1][1]*nums[i] if dp[i-1][1]<=0 else nums[i] + dp[i][1] = dp[i-1][0]*nums[i] if dp[i-1][0]>0 else nums[i] + + ans = max(ans, dp[i][0]) + + return ans + +""" +The above solution can further reduce the space complexity to O(1). +""" +class Solution(object): + def maxProduct(self, nums): + if not nums: return 0 + + maxLast = nums[0] + minLast = nums[0] + ans = nums[0] + + for i in xrange(1, len(nums)): + if nums[i]==0: + newMax = 0 + newMin = 0 + elif nums[i]>0: + newMax = maxLast*nums[i] if maxLast>0 else nums[i] + newMin = minLast*nums[i] if minLast<=0 else nums[i] + else: + newMax = minLast*nums[i] if minLast<=0 else nums[i] + newMin = maxLast*nums[i] if maxLast>0 else nums[i] + + maxLast = newMax + minLast = newMin + ans = max(ans, maxLast) + + return ans \ No newline at end of file diff --git a/problems/python/maximum-subarray-sum-with-one-deletion.py b/problems/python/maximum-subarray-sum-with-one-deletion.py new file mode 100755 index 0000000..a7b34f2 --- /dev/null +++ b/problems/python/maximum-subarray-sum-with-one-deletion.py @@ -0,0 +1,29 @@ +""" +dp[i][0] = max sum that ends at arr[i], not yet done any deletion +dp[i][1] = max sum that ends at arr[i], already done the deletion + +dp[i][0] = max(dp[i-1][0]+arr[i], arr[i]) +dp[i][1] = max(dp[i-1][0], dp[i-1][1]+arr[i], arr[i]) + +Time: O(N). +Space: O(N), can further reduce to O(1). +""" + +class Solution(object): + def maximumSum(self, arr): + if not arr: return arr + if len(arr)==1: return arr[0] + + dp = [[0, 0] for _ in xrange(len(arr))] + subarrayMaxSum = arr[0] + + for i in xrange(len(arr)): + if i==0: + dp[i][0] = arr[i] + dp[i][1] = arr[i] + else: + dp[i][0] = max(dp[i-1][0]+arr[i], arr[i]) + dp[i][1] = max(dp[i-1][0], dp[i-1][1]+arr[i]) + subarrayMaxSum = max(subarrayMaxSum, dp[i][0], dp[i][1]) + + return subarrayMaxSum \ No newline at end of file diff --git a/problems/maximum-subarray.py b/problems/python/maximum-subarray.py old mode 100644 new mode 100755 similarity index 67% rename from problems/maximum-subarray.py rename to problems/python/maximum-subarray.py index b04bc92..5b02f03 --- a/problems/maximum-subarray.py +++ b/problems/python/maximum-subarray.py @@ -19,4 +19,17 @@ def maxSubArray(self, nums): maxCurrent = [nums[0]] #[1] for i in xrange(1, len(nums)): maxCurrent.append(max(nums[i], nums[i]+maxCurrent[-1])) #[0] - return max(maxCurrent) #[2] \ No newline at end of file + return max(maxCurrent) #[2] + +# 2020/11/14 +class Solution(object): + def maxSubArray(self, nums): + ans = nums[0] + last_max = nums[0] + + for i in xrange(len(nums)): + if i==0: continue + last_max = max(nums[i], nums[i]+last_max) + ans = max(ans, last_max) + + return ans \ No newline at end of file diff --git a/problems/python/maximum-swap.py b/problems/python/maximum-swap.py new file mode 100755 index 0000000..ffca66a --- /dev/null +++ b/problems/python/maximum-swap.py @@ -0,0 +1,23 @@ +""" +1. Generate positions. Storing the mapping between number to indices. +2. Iterate from left, for each n1, find the largest number larger than n1 (searching from 9, 8, 7 to n1+1). +3. Since we need to find the max output. There might be multiple the same number, we need to find the index of the rightest number. +4. Remove n1 when it is done. Because right of the n1 should not consider it anymore. +""" +class Solution(object): + def maximumSwap(self, num): + numList = [int(n) for n in str(num)] + positions = collections.defaultdict(list) + for i, n in enumerate(numList): positions[n].append(i) #[1] + + i = 0 + for i, n1 in enumerate(numList): #[2] + n1 = numList[i] + for n2 in xrange(9, n1, -1): + if n2 in positions and len(positions[n2])>0: + j = positions[n2][-1] #[3] + numList[i], numList[j] = numList[j], numList[i] + return int(''.join([str(n) for n in numList])) + positions[n1].pop(0) #[4] + + return num \ No newline at end of file diff --git a/problems/python/maximum-units-on-a-truck.py b/problems/python/maximum-units-on-a-truck.py new file mode 100755 index 0000000..4e0ce10 --- /dev/null +++ b/problems/python/maximum-units-on-a-truck.py @@ -0,0 +1,17 @@ +class Solution(object): + def maximumUnits(self, boxTypes, truckSize): + sortedBox = [] + ans = 0 + + for count, units in boxTypes: + sortedBox.append((units, count)) + + sortedBox.sort() + + while sortedBox and truckSize>0: + units, count = sortedBox.pop() + d = min(count, truckSize) + truckSize -= d + ans += d*units + + return ans \ No newline at end of file diff --git a/problems/median-of-two-sorted-arrays.py b/problems/python/median-of-two-sorted-arrays.py old mode 100644 new mode 100755 similarity index 94% rename from problems/median-of-two-sorted-arrays.py rename to problems/python/median-of-two-sorted-arrays.py index e61ac70..7fe8045 --- a/problems/median-of-two-sorted-arrays.py +++ b/problems/python/median-of-two-sorted-arrays.py @@ -24,7 +24,7 @@ ```python max_left_A<=min_right_B and max_left_B<=min_right_A ``` -And if `min_right_Blen(B): A, B = B, A #[0] M, N = len(A), len(B) - l, h = 0, M + l, r = 0, M - while l<=h: - i = (h+l)/2 + while l<=r: + i = (r+l)/2 j = (M+N)/2-i #[1] max_left_A = A[i-1] if i>0 else float('-inf') #[2] @@ -73,7 +73,7 @@ def findMedianSortedArrays(self, A, B): else: return min(min_right_A, min_right_B) #[1] elif min_right_B= Y[after-i-1]: - h = i + r = i else: l = i + 1 i = l diff --git a/problems/python/meeting-rooms-ii.py b/problems/python/meeting-rooms-ii.py new file mode 100755 index 0000000..50cebde --- /dev/null +++ b/problems/python/meeting-rooms-ii.py @@ -0,0 +1,114 @@ +# Definition for an interval. +# class Interval(object): +# def __init__(self, s=0, e=0): +# self.start = s +# self.end = e + +class Solution(object): + def minMeetingRooms(self, intervals): + if len(intervals)==0: return 0 + + timeline = {} + room_open = 0 + result = 0 + + for inter in intervals: + if inter.start not in timeline: + timeline[inter.start] = [1, 0] + else: + timeline[inter.start][0]+=1 + if inter.end not in timeline: + timeline[inter.end] = [0, 1] + else: + timeline[inter.end][1]+=1 + + for time in timeline.keys().sort(): + room_open-=timeline[time][1] + room_open+=timeline[time][0] + result = max(result, room_open) + + return result + + + +# Definition for an interval. +# class Interval(object): +# def __init__(self, s=0, e=0): +# self.start = s +# self.end = e + +class Solution(object): + def minMeetingRooms(self, intervals): + if not intervals: return 0 + + free_rooms = [] + intervals.sort(key= lambda x: x.start) + + heapq.heappush(free_rooms, intervals[0].end) + for i in intervals[1:]: + if free_rooms[0]<=i.start: + heapq.heappop(free_rooms) + heapq.heappush(free_rooms, i.end) + + return len(free_rooms) + + + +#2021/8/12 +""" +Time: O(NLogN) +Space: O(N) + +1. Sort the intervals by the start time. So we can check the starttime in order. + +2. Declare `h`. Imagine we store the room (endtime) we need in the `h`. +If a meeting comes up, first we will check if any room avaliable. +If so, take the room. (Remove the old endtime from `h` and add the current endtime) +If not, start a new room. (Add the current endtime) +Use heap so it will be convinient for us to get the earliest endtime at h[0] (most avaliable room). + +3. At last, `h` will contain the rooms (endtimes) needed for all the intervals. +""" +class Solution(object): + def minMeetingRooms(self, intervals): + intervals.sort() + h = [intervals[0][1]] + + for i in xrange(1, len(intervals)): + start = intervals[i][0] + end = intervals[i][1] + + if start>=h[0]: + #the room at h[0] is avaliable + heapq.heappop(h) + heapq.heappush(h, end) + + return len(h) + + +""" +Time: O(NLogN) +Space: O(N) + +e0, s0 is one of the previous meetings where the end time is the earliest (smallest). +""" +class Solution(object): + def minMeetingRooms(self, intervals): + if not intervals: return 0 + ans = 1 + h = [] + + intervals.sort() + heapq.heappush(h, (intervals[0][1], intervals[0][0])) + + for i in xrange(1, len(intervals)): + s = intervals[i][0] + e = intervals[i][1] + + e0, s0 = h[0] + + if s>=e0: heapq.heappop(h) + heapq.heappush(h, (e, s)) + ans = max(ans, len(h)) + + return ans \ No newline at end of file diff --git a/problems/python/meeting-rooms.py b/problems/python/meeting-rooms.py new file mode 100755 index 0000000..797b422 --- /dev/null +++ b/problems/python/meeting-rooms.py @@ -0,0 +1,16 @@ +""" +Time: O(NLogN) +Space: O(1) + +Sort the interval (mainly by start time), see if the end overlaps with the start of next. +""" +class Solution(object): + def canAttendMeetings(self, intervals): + intervals.sort() + + for i in xrange(len(intervals)-1): + end = intervals[i][1] + nextStart = intervals[i+1][0] + if end>nextStart: return False + + return True \ No newline at end of file diff --git a/problems/merge-intervals.py b/problems/python/merge-intervals.py old mode 100644 new mode 100755 similarity index 55% rename from problems/merge-intervals.py rename to problems/python/merge-intervals.py index 7b6c192..4283b62 --- a/problems/merge-intervals.py +++ b/problems/python/merge-intervals.py @@ -14,11 +14,24 @@ class Solution(object): def merge(self, intervals): result = [] - intervals.sort(key=lambda x: x.start) #[1] + intervals.sort() #[1] for inter in intervals: - if len(result)>0 and result[-1].end>=inter.start: #[2] - result[-1].end = max(result[-1].end, inter.end) #[3] + if len(result)>0 and result[-1][1]>=inter[0]: #[2] + result[-1][1] = max(result[-1][1], inter[1]) #[3] else: #[4] result.append(inter) - return result \ No newline at end of file + return result + + +class Solution(object): + def merge(self, intervals): + intervals.sort() + + i = 0 + while i=intervals[i+1][0]: + intervals = intervals[:i] + [[min(intervals[i][0], intervals[i+1][0]), max(intervals[i][1], intervals[i+1][1])]] + intervals[i+2:] + else: + i += 1 + return intervals \ No newline at end of file diff --git a/problems/python/merge-k-sorted-lists.py b/problems/python/merge-k-sorted-lists.py new file mode 100755 index 0000000..4099ef5 --- /dev/null +++ b/problems/python/merge-k-sorted-lists.py @@ -0,0 +1,27 @@ +""" +Time: O(NLogK). Popping and pushing to a heap with K elements takes O(LogK). There are N nodes in total. +Space: O(1). + +All k lists are already sorted. +Put all k nodes in the head to the min heap. Each time, pop the one out. +Since it is a min heap the node popping out will be the smallest node. +For each node, attach it to the last node and push node.next to the heap. + +Note that, heapq are not able to sort ListNode, so we need to put tuple of node.val and node to the heap: `(node.val, node)` +So the heap will sort it by node.val. +""" +class Solution(object): + def mergeKLists(self, lists): + h = [(node.val, node) for node in lists if node] + heapq.heapify(h) + + preHead = ListNode() + curr = preHead + + while h: + _, node = heapq.heappop(h) + if node.next: heapq.heappush(h, (node.next.val, node.next)) + curr.next = node + curr = curr.next + + return preHead.next \ No newline at end of file diff --git a/problems/merge-sorted-array.py b/problems/python/merge-sorted-array.py old mode 100644 new mode 100755 similarity index 100% rename from problems/merge-sorted-array.py rename to problems/python/merge-sorted-array.py diff --git a/problems/merge-two-sorted-lists.py b/problems/python/merge-two-sorted-lists.py old mode 100644 new mode 100755 similarity index 100% rename from problems/merge-two-sorted-lists.py rename to problems/python/merge-two-sorted-lists.py diff --git a/problems/min-cost-climbing-stairs.py b/problems/python/min-cost-climbing-stairs.py old mode 100644 new mode 100755 similarity index 60% rename from problems/min-cost-climbing-stairs.py rename to problems/python/min-cost-climbing-stairs.py index 5e2fc97..8579cff --- a/problems/min-cost-climbing-stairs.py +++ b/problems/python/min-cost-climbing-stairs.py @@ -20,4 +20,23 @@ def minCostClimbingStairs(self, cost): else: memo[i] = min(memo[i-1]+cost[i-1], memo[i-2]+cost[i-2]) - return memo[-1] \ No newline at end of file + return memo[-1] + +#2020/11/9 +class Solution(object): + def minCostClimbingStairs(self, cost): + def helper(i): + if i in history: return history[i] + + if i>len(cost)-1: + history[i] = 0 + elif i==len(cost)-1 or i==len(cost)-2: + history[i] = cost[i] + elif i1: + currSum = heapq.heappop(h)+heapq.heappop(h) + cost += currSum + heapq.heappush(h, currSum) + + return cost \ No newline at end of file diff --git a/problems/python/minimum-cost-to-hire-k-workers.py b/problems/python/minimum-cost-to-hire-k-workers.py new file mode 100755 index 0000000..26bc929 --- /dev/null +++ b/problems/python/minimum-cost-to-hire-k-workers.py @@ -0,0 +1,45 @@ +""" +Time: O(NLogN) +Space: O(N) + +1. In the paidGroup, one of the worker will be paid by his minimum wage. +Otherwise, the cost can be lower. Let's call this person "captain". +"captain" is getting paid "wage". + +2. People in the paidGroup will be paid according to quality. +So people in the paidGroup is getting paid (individual quality) * (captain's wage/quality) +The total cost will be (sum of the quality in the paidGroup) * (captain's wage/quality). +So given a captain, we want the quality in the paidGroup as low as possible to have minimum cost. + +3. Declare a list of worker's info (ratio, quality, wage). +Sort the list. Sorting the list is important. +In the next step, we are going to iterate over the workers and assume they are "captain". +This make sure that we can afford to pay worker already in the paidGroup larger than their minimum wage. + +4. Iterate over the workers and assume they are "captain". +Also adding the worker to the paidGroup. +The paidGroup will at most have k people. With lowest quality. Using a max heap to maintain. +Also, tracking the total quality in the paidGroup (sumQ), so we can calculate the cost when `len(paidGroup)==k`. +Return the minimum cost. +""" +class Solution(object): + def mincostToHireWorkers(self, quality, wage, k): + cost = float('inf') + + workers = [(wage[i]/float(quality[i]), quality[i], wage[i]) for i in xrange(len(quality))] + workers = sorted(workers) + + paidGroup = [] + sumQ = 0 + + for r, q, w in workers: + heapq.heappush(paidGroup, -q) + sumQ += q + + if len(paidGroup)>k: + sumQ -= -heapq.heappop(paidGroup) + + if len(paidGroup)==k: + cost = min(cost, sumQ*r) + + return cost \ No newline at end of file diff --git a/problems/python/minimum-cost-to-make-at-least-one-valid-path-in-a-grid.py b/problems/python/minimum-cost-to-make-at-least-one-valid-path-in-a-grid.py new file mode 100755 index 0000000..2188f4a --- /dev/null +++ b/problems/python/minimum-cost-to-make-at-least-one-valid-path-in-a-grid.py @@ -0,0 +1,32 @@ +class Solution(object): + def minCost(self, grid): + pq = [(0, False, 0, 0, 0)] + visited = set() + M = len(grid) + N = len(grid[0]) + + while pq: + cost, modified, direction, x, y = heapq.heappop(pq) + if x<0 or x>=M or y<0 or y>=N: continue + if (direction, x, y) in visited: continue + visited.add((direction, x, y)) + + if x==M-1 and y==N-1: return cost + + if direction==0: direction = grid[x][y] + + if direction==1: + heapq.heappush(pq, (cost, False, 0, x, y+1)) + elif direction==2: + heapq.heappush(pq, (cost, False, 0, x, y-1)) + elif direction==3: + heapq.heappush(pq, (cost, False, 0, x+1, y)) + elif direction==4: + heapq.heappush(pq, (cost, False, 0, x-1, y)) + + if not modified: + for d in [1,2,3,4]: + if d==grid[x][y]: continue + heapq.heappush(pq, (cost+1, True, d, x, y)) + + return float('inf') \ No newline at end of file diff --git a/problems/python/minimum-cost-to-reach-city-with-discounts.py b/problems/python/minimum-cost-to-reach-city-with-discounts.py new file mode 100755 index 0000000..93205f3 --- /dev/null +++ b/problems/python/minimum-cost-to-reach-city-with-discounts.py @@ -0,0 +1,25 @@ +class Solution(object): + def minimumCost(self, n, highways, discounts): + pq = [(0, discounts, 0)] + visited = set() + + adj = collections.defaultdict(list) + for city1, city2, toll in highways: + adj[city1].append((city2, toll)) + adj[city2].append((city1, toll)) + + + while pq: + toll, d, city = heapq.heappop(pq) + if (d, city) in visited: continue + visited.add((d, city)) + + if city==n-1: return toll + + for nei, toll2 in adj[city]: + if (d, nei) not in visited: + heapq.heappush(pq, (toll+toll2, d, nei)) + if d>0 and (d-1, nei) not in visited: + heapq.heappush(pq, (toll+toll2/2, d-1, nei)) + + return -1 \ No newline at end of file diff --git a/problems/minimum-depth-of-binary-tree.py b/problems/python/minimum-depth-of-binary-tree.py old mode 100644 new mode 100755 similarity index 53% rename from problems/minimum-depth-of-binary-tree.py rename to problems/python/minimum-depth-of-binary-tree.py index b919ab1..1ea7e27 --- a/problems/minimum-depth-of-binary-tree.py +++ b/problems/python/minimum-depth-of-binary-tree.py @@ -18,4 +18,27 @@ def minDepth(self, root): if node.left: q.append((node.left, depth+1)) if node.right: - q.append((node.right, depth+1)) \ No newline at end of file + q.append((node.right, depth+1)) + + + +""" +Time: O(N) +Space: O(N) + +Standard BFS on a binary tree. +""" +class Solution(object): + def minDepth(self, root): + if not root: return 0 + q = collections.deque([(root, 1)]) + + while q: + node, depth = q.popleft() + + if not node.left and not node.right: return depth + + if node.left: q.append((node.left, depth+1)) + if node.right: q.append((node.right, depth+1)) + + return 'ERROR' \ No newline at end of file diff --git a/problems/python/minimum-difficulty-of-a-job-schedule.py b/problems/python/minimum-difficulty-of-a-job-schedule.py new file mode 100755 index 0000000..5ba8179 --- /dev/null +++ b/problems/python/minimum-difficulty-of-a-job-schedule.py @@ -0,0 +1,29 @@ +""" +dp[i][k] := minimum difficulty of a k days job schedule, D[:i]. +maxInRange[i][j] := max(D[i:j+1]) +""" + +class Solution(object): + def minDifficulty(self, D, K): + if not D or not K or K>len(D): return -1 + N = len(D) + + maxInRange = [[0 for _ in xrange(N)] for _ in xrange(N)] + for i in xrange(N): maxInRange[i][i] = D[i] + for l in xrange(2, N+1): + for i in xrange(N): + j = i+l-1 + if j>=N: continue + maxInRange[i][j] = max(maxInRange[i+1][j-1], D[i], D[j]) + + dp = [[float('inf') for _ in xrange(K+1)] for _ in xrange(N+1)] + dp[0][0] = 0 + + for i in xrange(1, N+1): + for k in xrange(1, min(i, K)+1): + for j in xrange(k, i+1): + dp[i][k] = min(dp[i][k], dp[j-1][k-1]+maxInRange[j-1][i-1]) + + #if you don't pre-calculate maxInRange + #dp[i][k] = min(dp[i][k], dp[j-1][k-1]+max(D[j-1:i])) + return dp[N][K] \ No newline at end of file diff --git a/problems/python/minimum-falling-path-sum-ii.py b/problems/python/minimum-falling-path-sum-ii.py new file mode 100755 index 0000000..cace34d --- /dev/null +++ b/problems/python/minimum-falling-path-sum-ii.py @@ -0,0 +1,48 @@ +""" +dp[i][j] = minimum sum of falling path that chooeses A[i][j] as ending +dp[i][j] = (min(dp[i-1]) except dp[i-1][j]) + A[i][j] + +implement min(dp[i-1]) except dp[i-1][j] by getMin() +Can further optimize getMin() by memorization. + +Time: O(NM). +Space: O(NM), can further reduce to O(M). +""" + +import collections +import heapq + +class Solution(object): + def minFallingPathSum(self, A): + def getMin(i, j): + minN = float('inf') + for idx, n in enumerate(dp[i]): + if j==idx: continue + minN = min(minN, n) + return minN + + if not A or not A[0]: return 0 + if len(A)==1: return min(A) + + history = collections.defaultdict(list) + + dp = [[0 for _ in xrange(len(A[0]))] for _ in xrange(len(A))] + + for i in xrange(len(A)): + for j in xrange(len(A[0])): + dp[i][j] = getMin(i-1, j) + A[i][j] + + return min(dp[-1]) +class Solution(object): + def minFallingPathSum(self, A): + if not A or not A[0]: return 0 + if len(A)==1: return min(A) + + dp = [[0 for _ in xrange(len(A[0]))] for _ in xrange(len(A))] + + for i in xrange(len(A)): + h = heapq.nsmallest(2, dp[i-1]) if i>0 else [0, 0] + for j in xrange(len(A[0])): + dp[i][j] = (h[1] if h[0]==dp[i-1][j] else h[0]) + A[i][j] + + return min(dp[-1]) \ No newline at end of file diff --git a/problems/python/minimum-knight-moves.py b/problems/python/minimum-knight-moves.py new file mode 100755 index 0000000..84cb400 --- /dev/null +++ b/problems/python/minimum-knight-moves.py @@ -0,0 +1,30 @@ +# Bidirectional BFS +class Solution(object): + def minKnightMoves(self, x, y): + offsets = [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)] + q1 = collections.deque([(0, 0)]) + q2 = collections.deque([(x, y)]) + steps1 = {(0, 0): 0} #steps needed starting from (0, 0) + steps2 = {(x, y): 0} #steps needed starting from (x,y) + + while q1 and q2: + i1, j1 = q1.popleft() + if (i1, j1) in steps2: return steps1[(i1, j1)]+steps2[(i1, j1)] + + i2, j2 = q2.popleft() + if (i2, j2) in steps1: return steps1[(i2, j2)]+steps2[(i2, j2)] + + for ox, oy in offsets: + nextI1 = i1+ox + nextJ1 = j1+oy + if (nextI1, nextJ1) not in steps1: + q1.append((nextI1, nextJ1)) + steps1[(nextI1, nextJ1)] = steps1[(i1, j1)]+1 + + nextI2 = i2+ox + nextJ2 = j2+oy + if (nextI2, nextJ2) not in steps2: + q2.append((nextI2, nextJ2)) + steps2[(nextI2, nextJ2)] = steps2[(i2, j2)]+1 + + return float('inf') \ No newline at end of file diff --git a/problems/python/minimum-number-of-flips-to-convert-binary-matrix-to-zero-matrix.py b/problems/python/minimum-number-of-flips-to-convert-binary-matrix-to-zero-matrix.py new file mode 100755 index 0000000..5559c80 --- /dev/null +++ b/problems/python/minimum-number-of-flips-to-convert-binary-matrix-to-zero-matrix.py @@ -0,0 +1,42 @@ +class Solution(object): + def minFlips(self, mat): + def flip(mat, m, n): + mat[m][n] = 0 if mat[m][n]==1 else 1 + if m+1=0: mat[m-1][n] = 0 if mat[m-1][n]==1 else 1 + if n-1>=0: mat[m][n-1] = 0 if mat[m][n-1]==1 else 1 + + def check(mat, state): + for i, b in enumerate(state): + if b=='1': + m = i/len(mat[0]) + n = i%len(mat[0]) + flip(mat, m, n) + + for i in xrange(len(mat)): + for j in xrange(len(mat[0])): + if mat[i][j]==1: return False + + return True + + + M = len(mat) + N = len(mat[0]) + q = collections.deque(['0'*(M*N)]) + visited = set() + + while q: + state = q.popleft() + if state in visited: continue + visited.add(state) + + if check([row[:] for row in mat], state): return state.count('1') + + for i in xrange(len(state)): + if state[i]=='1': continue + nextState = state[:i] + '1' +state[i+1:] + q.append(nextState) + + return -1 + \ No newline at end of file diff --git a/problems/minimum-path-sum.py b/problems/python/minimum-path-sum.py old mode 100644 new mode 100755 similarity index 100% rename from problems/minimum-path-sum.py rename to problems/python/minimum-path-sum.py diff --git a/problems/python/minimum-score-triangulation-of-polygon.py b/problems/python/minimum-score-triangulation-of-polygon.py new file mode 100755 index 0000000..96c60a3 --- /dev/null +++ b/problems/python/minimum-score-triangulation-of-polygon.py @@ -0,0 +1,16 @@ +class Solution(object): + def minScoreTriangulation(self, values): + N = len(values) + dp = [[float('inf')]*N for _ in xrange(N)] + + for i in xrange(N-1): + dp[i][i+1] = 0 + + for l in xrange(3, N+1): + for i in xrange(N-l+1): + j = i+l-1 + + for k in xrange(i+1, j): + dp[i][j] = min(dp[i][j], dp[i][k]+values[i]*values[k]*values[j]+dp[k][j]) + return dp[0][N-1] + \ No newline at end of file diff --git a/problems/python/minimum-size-subarray-sum.py b/problems/python/minimum-size-subarray-sum.py new file mode 100755 index 0000000..71e6aa7 --- /dev/null +++ b/problems/python/minimum-size-subarray-sum.py @@ -0,0 +1,20 @@ +""" +As we move right the i and increament s, if the s >= target, we + +""" +class Solution(object): + def minSubArrayLen(self, target, nums): + s = 0 + l = 0 + ans = float('inf') + + for i in xrange(len(nums)): + s += nums[i] + + while s>=target: + ans = min(i-l+1, ans) + s -= nums[l] + l += 1 + + return ans if ans!=float('inf') else 0 + \ No newline at end of file diff --git a/problems/python/minimum-swaps-to-group-all-1s-together.py b/problems/python/minimum-swaps-to-group-all-1s-together.py new file mode 100755 index 0000000..0f44bc6 --- /dev/null +++ b/problems/python/minimum-swaps-to-group-all-1s-together.py @@ -0,0 +1,33 @@ +""" +We know that, after swapping, the length of the continuous 1 will be equal to number of 1 in the data. Let's call it `L`. +So what we need to do is sliding window with length L and see what is the max 1 count within the window. +The window with max 1 count will need to do minimal swap. + +Time: O(N) +Space: O(1) +""" +class Solution(object): + def minSwaps(self, data): + L = 0 #sliding window length + #initialize L + for num in data: + if num==1: L += 1 + + count = 0 #count of 1 within the sliding window + maxCount = 0 #max count of 1 within the sliding window + + #initialize the count from data[0] to data[L-1] + for i in xrange(L): + if data[i]==1: count += 1 + maxCount = count + + #slide the window + for i in xrange(1, len(data)): + #i: start index of the sliding window + j = i+L-1 #end index of the sliding window + if j>=len(data): break + if data[j]==1: count += 1 + if data[i-1]==1: count -= 1 + maxCount = max(maxCount, count) + + return L-maxCount \ No newline at end of file diff --git a/problems/python/minimum-swaps-to-make-sequences-increasing.py b/problems/python/minimum-swaps-to-make-sequences-increasing.py new file mode 100755 index 0000000..73ac97b --- /dev/null +++ b/problems/python/minimum-swaps-to-make-sequences-increasing.py @@ -0,0 +1,19 @@ +class Solution(object): + def minSwap(self, A, B): + keep = [float('inf') for _ in xrange(len(A))] + swap = [float('inf') for _ in xrange(len(A))] + + keep[0] = 0 + swap[0] = 1 + + for i in xrange(1, len(A)): + + if A[i]>A[i-1] and B[i]>B[i-1]: + keep[i] = keep[i-1] + swap[i] = swap[i-1]+1 + + if A[i]>B[i-1] and B[i]>A[i-1]: + keep[i] = min(keep[i], swap[i-1]) + swap[i] = min(swap[i], keep[i-1]+1) + + return min(keep[-1], swap[-1]) \ No newline at end of file diff --git a/problems/python/minimum-time-difference.py b/problems/python/minimum-time-difference.py new file mode 100755 index 0000000..1645692 --- /dev/null +++ b/problems/python/minimum-time-difference.py @@ -0,0 +1,32 @@ +""" +Time: O(1440) +Space: O(1440) +""" +class Solution(object): + def findMinDifference(self, timeStrings): + def timeStringToMinutes(timeString): + time = timeString.split(':') + h = int(time[0]) + m = int(time[1]) + return h*60+m + + ans = float('inf') + minTime = float('inf') + maxTime = float('-inf') + timeSet = set() + for timeString in timeStrings: + t = timeStringToMinutes(timeString) + if t in timeSet: return 0 + minTime = min(minTime, t) + maxTime = max(maxTime, t) + timeSet.add(t) + + + prev = None + for t in xrange(minTime, maxTime+1): + if t not in timeSet: continue + if prev!=None: ans = min(ans, t-prev) + prev = t + + ans = min(ans, 1440+minTime-maxTime) #compare minTime and maxTime + return ans \ No newline at end of file diff --git a/problems/python/minimum-window-substring.py b/problems/python/minimum-window-substring.py new file mode 100755 index 0000000..d5d0e07 --- /dev/null +++ b/problems/python/minimum-window-substring.py @@ -0,0 +1,42 @@ +""" +Time: O(S+T) +Space: O(S+T) + +Moving pointers l and r to change the window. +The window we desired is the one that `actual==required`. (`windowCounter` and `tCounter` help us to track required and actual) +If the window is not what we desired, keep expand right. +If the window is what we desired, contract the window from left, see if we can find shorter ans. +""" +import collections + +class Solution(object): + def minWindow(self, s, t): + ans = s + ansFound = False + windowCounter = collections.Counter() + tCounter = collections.Counter(t) + required = len(tCounter) #number of unique char in t + actual = 0 #number of unique char in t in the window that has the count needed in tCounter + r = l = 0 + + while r=self.size: + self.sum -= self.q.popleft() + + self.sum += val + self.q.append(val) + return float(self.sum)/len(self.q) \ No newline at end of file diff --git a/problems/python/my-calendar-ii.py b/problems/python/my-calendar-ii.py new file mode 100755 index 0000000..8d76f69 --- /dev/null +++ b/problems/python/my-calendar-ii.py @@ -0,0 +1,27 @@ +""" +self.booked := booked times. +self.overlaps := overlaped times. + +For each new book, +1. check if the new book overlaps with the times in self.overlaps, if so, return False. +2. check if the new book overlaps with the times in self.booked, if so, store the overlaped time in self.overlaps. +3. store the new book in self.booked. +""" +class MyCalendarTwo(object): + + def __init__(self): + self.overlaps = [] + self.booked = [] + + + def book(self, start, end): + for s, e in self.overlaps: + if not (e<=start or end<=s): + return False + + for s, e in self.booked: + if not (e<=start or end<=s): + self.overlaps.append((max(start, s), min(end, e))) + + self.booked.append((start, end)) + return True \ No newline at end of file diff --git a/problems/n-ary-tree-level-order-traversal.py b/problems/python/n-ary-tree-level-order-traversal.py old mode 100644 new mode 100755 similarity index 100% rename from problems/n-ary-tree-level-order-traversal.py rename to problems/python/n-ary-tree-level-order-traversal.py diff --git a/problems/n-ary-tree-postorder-traversal.py b/problems/python/n-ary-tree-postorder-traversal.py old mode 100644 new mode 100755 similarity index 100% rename from problems/n-ary-tree-postorder-traversal.py rename to problems/python/n-ary-tree-postorder-traversal.py diff --git a/problems/n-ary-tree-preorder-traversal.py b/problems/python/n-ary-tree-preorder-traversal.py old mode 100644 new mode 100755 similarity index 100% rename from problems/n-ary-tree-preorder-traversal.py rename to problems/python/n-ary-tree-preorder-traversal.py diff --git a/problems/nested-list-weight-sum.py b/problems/python/nested-list-weight-sum.py old mode 100644 new mode 100755 similarity index 100% rename from problems/nested-list-weight-sum.py rename to problems/python/nested-list-weight-sum.py diff --git a/problems/python/network-delay-time.py b/problems/python/network-delay-time.py new file mode 100755 index 0000000..aa67bb0 --- /dev/null +++ b/problems/python/network-delay-time.py @@ -0,0 +1,213 @@ +""" +Dijkstra's algorithm heap implementation +first, we build an adjacent list. [0] +if you don't know what is adjacent list, see https://www.khanacademy.org/computing/computer-science/algorithms/graph-representation/a/representing-graphs + +we use a hash-map 'dis' to keep track of every node's distance to K. [1] +and we use a priority queue 'pq' to store all the node we encounter and its distance to K. [2] +which we use a tuple (distance to K, node) + +for every node we visit, if its distance to K is determined, we don't need to look at it anymore. [3] +because we always pop the nearest one to the K in the priority queue, we can be sure that the distance in 'dis' is the shortest. +then from the node, which we know the shortest path from K to the node, we keep on explore its neighbors. [4] + +then if we didn't visit every node we return -1, else we return the node which it takes the longest time to reach. [5] + +for time complexity +we use O(E) to make an adjacency list from edge list. +we will go through the while loop V-1 times because we need to determined the distance of V-1 other nodes. +for every loop + we pop from priority queue (here need O(logE) to get the nearest node). + we push the node's neighbor to the priority queue d times (which is the degree of the node) + push the node takes O(logE), we assume all the node is in the queue. + so every loop we use O(d*logE+logE)~=O(d*logE) +so total for total, it is O((V-1)*(d*logE))+O(E), we know that V-1~=V and V*d=E +so it is O(ElogE)+O(E)~=O(ElogE) +E is the number of edges, which is the length of 'times' of the input +V is the number of node, which is the 'N' of the inout + +for space complexity +we used O(V) and O(E) in 'dis' and 'pq', and O(E) on the adjacency list. +so, it is O(V+E). +""" +class Solution(object): + def networkDelayTime(self, times, N, K): + aj_list = collections.defaultdict(list) #[0] + for u, v, w in times: + aj_list[u].append((w, v)) + + dis = {} #[1] + pq = [(0, K)] #[2] + + while pq: + if len(dis)==N: break + + d, node = heapq.heappop(pq) + if node in dis: continue #[3] + + dis[node] = d + + for d2, nb in aj_list[node]: #[4] + if nb not in dis: #[3] + heapq.heappush(pq, (d+d2, nb)) #[2] + + return max(dis.values()) if len(dis)==N else -1 #[5] + + +#2020/10/17 +class Solution(object): + def networkDelayTime(self, times, N, K): + ans = float('-inf') + h = [(0, K)] + visited = set() + G = collections.defaultdict(list) + + for u, v, w in times: + G[u].append((v, w)) + + while h: + time, node = heapq.heappop(h) + + if node in visited: continue + visited.add(node) + ans = max(ans, time) + + if len(visited)==N: return time + for nei, time_to_nei in G[node]: + heapq.heappush(h, (time+time_to_nei, nei)) + + return -1 + + + + + + + +class Solution(object): + def networkDelayTime(self, times, n, k): + G = collections.defaultdict(list) #adjacency list + D = [float('inf')]*(n+1) #D[n] store the distance between n and k + D[k] = 0 + h = [(0, k)] + visited = set() + + #form the djacency list + for u, v, w in times: + G[u].append((v, w)) + + #dijkstra + while h: + d1, node = heapq.heappop(h) + if node in visited: continue + visited.add(node) + + for nei, d2 in G[node]: + if d1+d29 else '0'+str(h) + if all([c in digits for c in hs]): return hs + return '' + + def getSmallestHour(): + for h in xrange(0, 24): + hs = str(h) if h>9 else '0'+str(h) + if all([c in digits for c in hs]): return hs + return '' + + def getNextMinute(): + currentMinute = int(time[3]+time[4]) if time[3]!='0' else int(time[4]) + + for m in xrange(currentMinute+1, 60): + ms = str(m) if m>9 else '0'+str(m) + if all([c in digits for c in ms]): return ms + return '' + + def getSmallestMinute(): + for m in xrange(0, 60): + ms = str(m) if m>9 else '0'+str(m) + if all([c in digits for c in ms]): return ms + return '' + + digits = set([time[0], time[1], time[3], time[4]]) + nextMinute = getNextMinute() + if nextMinute: + return time[:3]+nextMinute + else: + return (getNextHour() or getSmallestHour())+':'+getSmallestMinute() \ No newline at end of file diff --git a/problems/python/next-permutation.py b/problems/python/next-permutation.py new file mode 100755 index 0000000..0a816ee --- /dev/null +++ b/problems/python/next-permutation.py @@ -0,0 +1,61 @@ +""" +This answer is the python version of the offical answer. + +Time: O(N) +Space: O(1) +""" +class Solution(object): + def nextPermutation(self, nums): + def reverse(start): + end = len(nums)-1 + + while start=0 and nums[i+1]<=nums[i]: + i -= 1 + + if i>=0: + j = len(nums)-1 + while nums[j]<=nums[i]: j -= 1 + swap(i, j) + + reverse(i+1) + return nums + +""" +Next Permutation means find the next (slightly) larger number using nums. + +1. Iterate from right, find the first num that is smaller. That's the one we are going to swap. => nums[i] +2. From the nums right to nums[i], find the smallest num that is larger than nums[i] => nums[j] +Since the right of the i must be an increasing sequence (looking from right), the first one that larger than nums[i] is the smallest one that is larger than nums[i] +3. Swap nums[i] and nums[j] +4. sort nums[i+1:] it will be the smallest permutaion. +5. Note that when an list is in increasing order looking from right, we can use `reverse` to sort it. +""" +class Solution(object): + def nextPermutation(self, nums): + def reverse(nums, l, r): + while l<=r: + nums[l], nums[r] = nums[r], nums[l] + l += 1 + r -= 1 + + i = len(nums)-2 + while i>=0 and nums[i]>=nums[i+1]: i -= 1 #[1] + + if i==-1: + return reverse(nums, 0, len(nums)-1) #nums is the largest permutation, sort nums + else: + j = len(nums)-1 + while j>i and nums[j]<=nums[i]: j -= 1 #[2] + nums[i], nums[j] = nums[j], nums[i] #[3] + reverse(nums, i+1, len(nums)-1) #[4] \ No newline at end of file diff --git a/problems/number-complement.py b/problems/python/number-complement.py old mode 100644 new mode 100755 similarity index 100% rename from problems/number-complement.py rename to problems/python/number-complement.py diff --git a/problems/python/number-of-connected-components-in-an-undirected-graph.py b/problems/python/number-of-connected-components-in-an-undirected-graph.py new file mode 100755 index 0000000..dbd37c2 --- /dev/null +++ b/problems/python/number-of-connected-components-in-an-undirected-graph.py @@ -0,0 +1,50 @@ +class Solution(object): + def countComponents(self, N, edges): + def dfs(start): + stack = [start] + + while stack: + node = stack.pop() + if node in visited: continue + visited.add(node) + + for nei in g[node]: + stack.append(nei) + + g = collections.defaultdict(list) + visited = set() + count = 0 + + for n1, n2 in edges: + g[n1].append(n2) + g[n2].append(n1) + + for n in xrange(N): + if n in visited: continue + dfs(n) + count += 1 + + return count + + +class Solution(object): + def countComponents(self, n, edges): + def find(n): + p = parents[n] + while p!=parents[p]: + p = find(p) + parents[n] = p + return p + + def union(n1, n2): + p1, p2 = find(n1), find(n2) + if p1==p2: return False + parents[p2] = p1 + return True + + count = n + parents = [n for n in xrange(n)] + + for n1, n2 in edges: + if union(n1, n2): count -= 1 + return count \ No newline at end of file diff --git a/problems/python/number-of-islands-ii.py b/problems/python/number-of-islands-ii.py new file mode 100755 index 0000000..a3893ae --- /dev/null +++ b/problems/python/number-of-islands-ii.py @@ -0,0 +1,50 @@ +""" +For each new land, the count will -= ((number of neighbors) - 1). +Because some for this new land introduced, some of the separate islands will be connected. +Note that, "number of neighbors", islands connected does not count. So for each neighbor, we use "find" to find its root. +""" +class Solution(object): + def numIslands2(self, M, N, positions): + def coorToNum(r, c): + return N*r+c + + def find(n): + p = parents[n] + while p!=parents[p]: + p = find(p) + parents[n] = p + return parents[n] + + def union(n1, n2): + p1 = find(n1) + p2 = find(n2) + + if p1==p2: return + parents[p1] = p2 + + count = 0 + lands = set() + ans = [] + parents = [n for n in xrange(M*N)] + + for r, c in positions: + n = coorToNum(r, c) + + if n in lands: + ans.append(count) + continue + + neis = set() + if r+1nums[j]: + if L[j]+1==L[i]: + # If L[j]+1==L[i], it means that the combination of L[j] can simply append nums[i] and reach L[i] + # So add the C[j] to C[i] + C[i] += C[j] + elif L[j]+1>L[i]: + # If L[j]+1==L[i], it means that the combination of L[j] can simply append nums[i] and exceed L[i] + # So update L[i] to L[j]+1 and C[i] to C[j] + L[i] = L[j]+1 + C[i] = C[j] + + max_length = max(L) + max_length_count = 0 + for i, count in enumerate(C): + if L[i]==max_length: + max_length_count+=count + return max_length_count + +""" +Time: O(N^2) +Spance: O(N) +""" + + +class Solution(object): + def findNumberOfLIS(self, nums): + dp = [1]*len(nums) + count = [1]*len(nums) + count[0] = 1 + + for i in xrange(1, (len(nums))): + for j in xrange(i-1, -1, -1): + if nums[i]>nums[j]: + if dp[j]+1>dp[i]: + count[i] = count[j] + dp[i] = dp[j]+1 + elif dp[j]+1==dp[i]: + count[i]+=count[j] + print count + + maxLis = max(dp) + ans = 0 + for i, lis in enumerate(dp): + if lis==maxLis: ans += count[i] + + return ans +""" +dp[i] := longest increasing subsequence that ends at nums[i] +count[i] := number of comfination that ends up dp[i]. + +dp[i] = max{ dp[j] where nums[i]>nums[j], j = 0~i-1 } + 1 + +Time: O(N^2) +Space: O(N) +""" \ No newline at end of file diff --git a/problems/python/number-of-matching-subsequences.py b/problems/python/number-of-matching-subsequences.py new file mode 100755 index 0000000..fd97676 --- /dev/null +++ b/problems/python/number-of-matching-subsequences.py @@ -0,0 +1,25 @@ +""" +Time: O(WL x LogS), W is the length of words, L is the length of word. +LogS is the time for binary search and S should be the count of repetitive words, we can assume it is Log(S/26) ~= LogS. + +Space: O(S) +""" +class Solution(object): + def numMatchingSubseq(self, s, words): + def match(position, word): + prev = -1 + for c in word: + if c not in position: return False + i = bisect.bisect_left(position[c], prev+1) + if i==len(position[c]): return False + prev = position[c][i] + return True + + position = collections.defaultdict(list) + count = 0 + for i, c in enumerate(s): + position[c].append(i) + + for word in words: + if match(position, word): count += 1 + return count \ No newline at end of file diff --git a/problems/python/number-of-provinces.py b/problems/python/number-of-provinces.py new file mode 100755 index 0000000..a0edfd7 --- /dev/null +++ b/problems/python/number-of-provinces.py @@ -0,0 +1,24 @@ +class Solution(object): + def findCircleNum(self, isConnected): + def bfs(startNode): + q = collections.deque([startNode]) + + while q: + node = q.popleft() + if node in visited: continue + visited.add(node) + + for nei in xrange(N): + if nei!=node and isConnected[node][nei]==1: + q.append(nei) + + N = len(isConnected) + visited = set() + ans = 0 + + for node in xrange(N): + if node in visited: continue + bfs(node) #put all the nodes in the same province to visited + ans += 1 + + return ans \ No newline at end of file diff --git a/problems/number-of-recent-calls.py b/problems/python/number-of-recent-calls.py old mode 100644 new mode 100755 similarity index 100% rename from problems/number-of-recent-calls.py rename to problems/python/number-of-recent-calls.py diff --git a/problems/python/number-of-squareful-arrays.py b/problems/python/number-of-squareful-arrays.py new file mode 100755 index 0000000..b27fdd6 --- /dev/null +++ b/problems/python/number-of-squareful-arrays.py @@ -0,0 +1,36 @@ +""" +Time: O(N!) +Space: O(N!) + +Create a helper function: +If there is no remains, ans += 1 +Check the last num in the permutaion, last +For each num in remains see if it and the last sum to a square number +If true, call helper + +Also, if the number is the same as the previous one, skip it, since we already explore the number. +""" + +class Solution(object): + def __init__(self): + self.ans = 0 + + def numSquarefulPerms(self, nums): + def isSquare(num): + return int(math.sqrt(num))**2==num + + def helper(per, remains): + if not remains: self.ans += 1 + last = per[-1] + + for i, n in enumerate(remains): + if i>0 and n==remains[i-1]: continue + if isSquare(last+n): + helper(per+[n], remains[:i]+remains[i+1:]) + + nums.sort() + for i, n in enumerate(nums): + if i>0 and n==nums[i-1]: continue + helper([nums[i]], nums[:i]+nums[i+1:]) + + return self.ans \ No newline at end of file diff --git a/problems/python/number-of-substrings-containing-all-thre.py b/problems/python/number-of-substrings-containing-all-thre.py new file mode 100755 index 0000000..66d43cc --- /dev/null +++ b/problems/python/number-of-substrings-containing-all-thre.py @@ -0,0 +1,22 @@ +class Solution(object): + def numberOfSubstrings(self, s): + #number of subarrays that at most have k unique char + def atMost(k): + counter = collections.Counter() + uniqueCount = 0 + ans = 0 + i = 0 + + for j, c in enumerate(s): + counter[c] += 1 + if counter[c]==1: uniqueCount += 1 + + while uniqueCount>k: + counter[s[i]] -= 1 + if counter[s[i]]==0: uniqueCount-= 1 + i += 1 + ans += j-i+1 + return ans + + n = len(s) + return atMost(3) - atMost(2) \ No newline at end of file diff --git a/problems/python/number-of-ways-to-arrive-at-destination.py b/problems/python/number-of-ways-to-arrive-at-destination.py new file mode 100755 index 0000000..3231254 --- /dev/null +++ b/problems/python/number-of-ways-to-arrive-at-destination.py @@ -0,0 +1,33 @@ +class Solution(object): + def countPaths(self, n, roads): + def countWaysToReach(node): + if node==0: return 1 + if node in history: return history[node] + c = 0 + for nei, t in adj[node]: + if nei in times and times[nei]+t==times[node]: + c += countWaysToReach(nei) + history[node] = c + return c + + history = {} #cache for countWaysToReach() + times = {} #min times to reach node n-1 + pq = [(0, 0)] + + adj = collections.defaultdict(list) + for u, v, t in roads: + adj[u].append((v, t)) + adj[v].append((u, t)) + + while pq: + t, node = heapq.heappop(pq) + if node in times: continue + times[node] = t + + if node==n-1: break + + for nei, t2 in adj[node]: + if nei in times: continue + heapq.heappush(pq, (t+t2, nei)) + + return countWaysToReach(n-1)%(10**9 + 7) \ No newline at end of file diff --git a/problems/odd-even-jump.py b/problems/python/odd-even-jump.py old mode 100644 new mode 100755 similarity index 100% rename from problems/odd-even-jump.py rename to problems/python/odd-even-jump.py diff --git a/problems/python/ones-and-zeroes.py b/problems/python/ones-and-zeroes.py new file mode 100755 index 0000000..5be3de7 --- /dev/null +++ b/problems/python/ones-and-zeroes.py @@ -0,0 +1,22 @@ +""" +dp[i][n][m] := max size of the subset with total m 0's and n 1's. +Find max size for each m<=M and n<=N. +""" +class Solution(object): + def findMaxForm(self, strs, M, N): + dp = [[[0 for _ in xrange(M+1)] for _ in xrange(N+1)] for _ in xrange(len(strs)+1)] + + for i in xrange(1, len(strs)+1): + count0 = strs[i-1].count('0') + count1 = len(strs[i-1])-count0 + + for n in xrange(N+1): + for m in xrange(M+1): + dp[i][n][m] = max(dp[i-1][n][m], (dp[i-1][n-count1][m-count0]+1) if m>=count0 and n>=count1 else 0) + + ans = 0 + for n in xrange(N+1): + for m in xrange(M+1): + ans = max(ans, dp[-1][n][m]) + + return ans \ No newline at end of file diff --git a/problems/open-the-lock.py b/problems/python/open-the-lock.py old mode 100644 new mode 100755 similarity index 100% rename from problems/open-the-lock.py rename to problems/python/open-the-lock.py diff --git a/problems/python/out-of-boundary-paths.py b/problems/python/out-of-boundary-paths.py new file mode 100755 index 0000000..03fbd63 --- /dev/null +++ b/problems/python/out-of-boundary-paths.py @@ -0,0 +1,26 @@ +""" +dp[k][i][j] := number of ways to get to (i, j) +dp[k+1][i][j] = sum(dp[k][x][y]) for all (x, y) that can get to (i, j) +For each k and i and j, also accumulate the ans + +Time: O(KMN) +Space: O(KMN) +""" + +class Solution(object): + def findPaths(self, M, N, K, i, j): + ans = 0 + + dp = [[[0 for _ in xrange(N)] for _ in xrange(M)] for _ in xrange(K+1)] + dp[0][i][j] = 1 + + for k in xrange(K): + for i in xrange(M): + for j in xrange(N): + if dp[k][i][j]>0: + for x, y in [(i+1, j), (i-1, j), (i, j+1), (i, j-1)]: + if x<0 or x>=M or y<0 or y>=N: + ans+=dp[k][i][j] + else: + dp[k+1][x][y]+=dp[k][i][j] + return ans % (1000000007) \ No newline at end of file diff --git a/problems/python/pacific-atlantic-water-flow.py b/problems/python/pacific-atlantic-water-flow.py new file mode 100755 index 0000000..15186ff --- /dev/null +++ b/problems/python/pacific-atlantic-water-flow.py @@ -0,0 +1,30 @@ +class Solution(object): + def pacificAtlantic(self, heights): + def bfs(q, ocian): + while q: + i0, j0 = q.popleft() + if (i0, j0) in ocian: continue + ocian.add((i0, j0)) + + for i, j in [(i0+1, j0), (i0-1, j0), (i0, j0+1), (i0, j0-1)]: + if i>=len(heights) or i<0 or j>=len(heights[0]) or j<0: continue + if heights[i][j]>=heights[i0][j0]: q.append((i, j)) + + pacific = set() + altalantic = set() + q1 = collections.deque() + q2 = collections.deque() + + #add top, left to pacific + for j in xrange(len(heights[0])): q1.append((0, j)) + for i in xrange(len(heights)): q1.append((i, 0)) + + #add right, bottom to atalantic + for j in xrange(len(heights[0])): q2.append((len(heights)-1, j)) + for i in xrange(len(heights)): q2.append((i, len(heights[0])-1)) + + bfs(q1, pacific) + bfs(q2, altalantic) + + return pacific.intersection(altalantic) + \ No newline at end of file diff --git a/problems/python/pairs-of-songs-with-total-durations-divisible-by-60.py b/problems/python/pairs-of-songs-with-total-durations-divisible-by-60.py new file mode 100755 index 0000000..d4d7fa6 --- /dev/null +++ b/problems/python/pairs-of-songs-with-total-durations-divisible-by-60.py @@ -0,0 +1,10 @@ +class Solution(object): + def numPairsDivisibleBy60(self, times): + counter = collections.Counter() + ans = 0 + + for time in times: + time = time%60 + ans += counter[60-time if time!=0 else 0] + counter[time] += 1 + return ans \ No newline at end of file diff --git a/problems/palindrome-number.py b/problems/python/palindrome-number.py old mode 100644 new mode 100755 similarity index 100% rename from problems/palindrome-number.py rename to problems/python/palindrome-number.py diff --git a/problems/python/palindrome-pairs.py b/problems/python/palindrome-pairs.py new file mode 100755 index 0000000..75e5082 --- /dev/null +++ b/problems/python/palindrome-pairs.py @@ -0,0 +1,62 @@ +""" +Time: O(N x L^2), assume the average length of word is L. Note that, eversing the string takes O(L). +Space: O(L), beside from ans, need `left` and `right` constantly to store the word. +""" +class Solution(object): + def palindromePairs(self, words): + ans = set() + index = {word:i for i, word in enumerate(words)} + + for i, word in enumerate(words): + for j in xrange(len(word)+1): + left = word[:j] + right = word[j:] + + # check if any other word that concat to the left will make palindrome: "OTHER_WORD+`left`+`right`" + # The above will be palindrome only if + # 1. `left` is palindrome (left==left[::-1]) + # 2. Exsit an "OTHER_WORD" in word in words that equals to the reverse of `right` (right[::-1] in index and index[right[::-1]]!=i). + if left==left[::-1]: + if right[::-1] in index and index[right[::-1]]!=i: + ans.add((index[right[::-1]], i)) + + # check if any other word that concat to the right will make palindrome: "`left`+`right`+OTHER_WORD" + # The above will be palindrome only if + # 1. `rihgt` is palindrome (right==right[::-1]) + # 2. Exsit an "OTHER_WORD" in words that equals to the reverse of `left` (left[::-1] in index and index[left[::-1]]!=i). + if right==right[::-1]: + if left[::-1] in index and index[left[::-1]]!=i: + ans.add((i, index[left[::-1]])) + + return ans + + +""" +Time: O(N^2 x L), will cause TLE. +Space: O(L), can reduce to O(1) by improving isPalindrome() +""" +class Solution(object): + def palindromePairs(self, words): + N = len(words) + ans = [] + + for i in xrange(N): + for j in xrange(N): + if i==j: continue + if self.isPalindrome(words[i]+words[j]): + ans.append([i, j]) + return ans + + def isPalindrome(self, word): + l = 0 + r = len(word)-1 + + while l<=r: + if word[l]==word[r]: + l += 1 + r -= 1 + else: + return False + return True + + \ No newline at end of file diff --git a/problems/python/palindrome-partitioning-iii.py b/problems/python/palindrome-partitioning-iii.py new file mode 100755 index 0000000..65fc55b --- /dev/null +++ b/problems/python/palindrome-partitioning-iii.py @@ -0,0 +1,53 @@ +""" +dp[i][k] := the minimal number of characters that needed to change for s[:i] with k disjoint substrings +""" +class Solution(object): + def palindromePartition(self, s, K): + def count(r, l): + c = 0 + + while r>l: + if s[r]!=s[l]: c += 1 + r -= 1 + l += 1 + return c + + N = len(s) + + dp = [[float('inf') for _ in xrange(K+1)] for _ in xrange(N+1)] + dp[0][0] = 0 + + for i in xrange(1, N+1): + for k in xrange(1, min(K, i)+1): + for j in xrange(k, i+1): + dp[i][k] = min(dp[i][k], dp[j-1][k-1] + count(i-1, j-1)) + + return dp[N][K] + +""" +The above `count()` are able to use dp technique to optimize. +dp[i][k] := the minimal number of characters that needed to change for s[:i] with k disjoint substrings +count[i][j] := the operation needed for s[i:j+1] become palindrome. +""" +class Solution(object): + def palindromePartition(self, s, K): + N = len(s) + + count = [[0 for _ in xrange(N)] for _ in xrange(N)] + for i in xrange(N): count[i][i] = 0 + + for l in xrange(2, N+1): + for i in xrange(N): + j = i+l-1 + if j>=N: continue + count[j][i] = count[j-1][i+1] + (0 if s[i]==s[j] else 1) + + dp = [[float('inf') for _ in xrange(K+1)] for _ in xrange(N+1)] + dp[0][0] = 0 + + for i in xrange(1, N+1): + for k in xrange(1, min(K, i)+1): + for j in xrange(k, i+1): + dp[i][k] = min(dp[i][k], dp[j-1][k-1] + count[i-1][j-1]) + + return dp[N][K] \ No newline at end of file diff --git a/problems/palindrome-partitioning.py b/problems/python/palindrome-partitioning.py old mode 100644 new mode 100755 similarity index 95% rename from problems/palindrome-partitioning.py rename to problems/python/palindrome-partitioning.py index cf4536b..e9000db --- a/problems/palindrome-partitioning.py +++ b/problems/python/palindrome-partitioning.py @@ -11,4 +11,4 @@ def search(s, pal_list): search(s[i:], pal_list+[s[:i]]) opt = [] search(S, []) - return opt + return opt \ No newline at end of file diff --git a/problems/python/palindromic-substrings.py b/problems/python/palindromic-substrings.py new file mode 100755 index 0000000..a375af6 --- /dev/null +++ b/problems/python/palindromic-substrings.py @@ -0,0 +1,61 @@ +""" +dp[i][j] := if s[i:j+1] is palindrome. +1. all length==1 string is palindrome. +2. all length==2 string is palindrome if two are the same. +3. all length>=3 string is palindrome if the outer most two char is the same and have an palindrome string inside. + +Time: O(N^2) +Space: 0(N^2) +""" +class Solution(object): + def countSubstrings(self, s): + N = len(s) + dp = [[False]*N for _ in xrange(N)] + ans = 0 + + #1 + for i in xrange(N): + dp[i][i] = True + ans += 1 + + #2 + for i in xrange(N-1): + if s[i]==s[i+1]: + dp[i][i+1] = True + ans += 1 + + #3 + for l in xrange(3, N+1): + for i in xrange(N): + j = i+l-1 + if j>=N: continue + if dp[i+1][j-1] and s[i]==s[j]: + dp[i][j] = True + ans += 1 + return ans + +""" +Iterate through the string, expand around each character. +Like this solution more since it is more intuitive. + +Time: O(N^2) +Space: O(1) +""" +class Solution(object): + def countSubstrings(self, s): + def count(l, r, N): + count = 0 + + while l>=0 and rr[-1]: + r.append(charRange[c][1]) + else: + r[-1] = max(r[-1], charRange[c][1]) + + ans = [] + for i in xrange(len(r)): + if i==0: + ans.append(r[i]+1) + else: + ans.append(r[i]-r[i-1]) + return ans + +""" +This offical answer is even cleaner. +""" +class Solution(object): + def partitionLabels(self, s): + lastSeen = {} + for i, c in enumerate(s): lastSeen[c] = i + + ans = [] + start = end = 0 + for i, c in enumerate(s): + end = max(end, lastSeen[c]) + if i==end: + ans.append(end-start+1) + start = i+1 + return ans \ No newline at end of file diff --git a/problems/python/partition-to-k-equal-sum-subsets.py b/problems/python/partition-to-k-equal-sum-subsets.py new file mode 100755 index 0000000..13c2411 --- /dev/null +++ b/problems/python/partition-to-k-equal-sum-subsets.py @@ -0,0 +1,41 @@ +class Solution(object): + def canPartitionKSubsets(self, nums, k): + def search(subs): + if not nums: return True + n = nums.pop() + for i, sub in enumerate(subs): + if sub+n<=target: + subs[i]+=n + if search(subs): return True + subs[i]-=n + if not sub: break + nums.append(n) + return False + + if sum(nums)%k!=0: return False + target = sum(nums)/k + nums.sort() + return search([0]*k) + + + +class Solution(object): + def canPartitionKSubsets(self, nums, k): + def dfs(currSum, k, s=0): + if k==0: return True + if currSum==target: return dfs(0, k-1) + + for i in xrange(s, N): + num = nums[i] + if not visited[i] and num+currSum<=target: + visited[i] = True + if dfs(currSum+num, k, i+1): return True + visited[i] = False + return False + + target, remain = divmod(sum(nums), k) + if remain>0: return False + + N = len(nums) + visited = [False]*N + return dfs(target, k) \ No newline at end of file diff --git a/problems/python/path-sum-ii.py b/problems/python/path-sum-ii.py new file mode 100755 index 0000000..2bfcda5 --- /dev/null +++ b/problems/python/path-sum-ii.py @@ -0,0 +1,42 @@ +class Solution(object): + def pathSum(self, root, S): + if not root: return False + + opt = [] + stack = [] + stack.append((root, 0, [])) + + while stack: + node, s, path = stack.pop() + s += node.val + path = path + [node.val] + if not node.left and not node.right and s==S: opt.append(path) + if node.left: stack.append((node.left, s, path)) + if node.right: stack.append((node.right, s, path)) + return opt +""" +Time complexity is O(N), because we traverse all the nodes. +Space complexity is O(N^2), because in the worst case, all node could carry all the other nodes in the `path`. +""" + + + +""" +Time complexity is O(N), because we traverse all the nodes. +Space complexity is O(N^2), because in the worst case, all node could carry all the other nodes in the `path`. +""" +class Solution(object): + def pathSum(self, root, targetSum): + if not root: return [] + + ans = [] + stack = [(root, root.val, [root.val])] + + while stack: + node, total, path = stack.pop() + + if not node.left and not node.right and total==targetSum: ans.append(path) + if node.left: stack.append((node.left, total+node.left.val, path+[node.left.val])) + if node.right: stack.append((node.right, total+node.right.val, path+[node.right.val])) + + return ans \ No newline at end of file diff --git a/problems/path-sum-iii.py b/problems/python/path-sum-iii.py old mode 100644 new mode 100755 similarity index 100% rename from problems/path-sum-iii.py rename to problems/python/path-sum-iii.py diff --git a/problems/python/path-sum.py b/problems/python/path-sum.py new file mode 100755 index 0000000..ce50d04 --- /dev/null +++ b/problems/python/path-sum.py @@ -0,0 +1,37 @@ +class Solution(object): + def hasPathSum(self, root, S): + if not root: return False + + stack = [] + stack.append((root, 0)) + + while stack: + node, s = stack.pop() + s += node.val + if not node.left and not node.right and s==S: return True + if node.left: stack.append((node.left, s)) + if node.right: stack.append((node.right, s)) + return False + + +""" +Time: O(N) +Space: O(N) + +DFS +""" +class Solution(object): + def hasPathSum(self, root, targetSum): + if not root: return False + + stack = [(root, root.val)] + + while stack: + node, total = stack.pop() + + if not node.left and not node.right and total==targetSum: return True + if node.left: stack.append((node.left, total+node.left.val)) + if node.right: stack.append((node.right, total+node.right.val)) + + return False + \ No newline at end of file diff --git a/problems/python/path-with-maximum-probability.py b/problems/python/path-with-maximum-probability.py new file mode 100755 index 0000000..db1b1d4 --- /dev/null +++ b/problems/python/path-with-maximum-probability.py @@ -0,0 +1,24 @@ +class Solution(object): + def maxProbability(self, n, edges, succProb, start, end): + pq = [(-1, start)] + visited = set() + adj = collections.defaultdict(list) + for i in xrange(len(edges)): + a, b = edges[i] + p = succProb[i] + adj[a].append((b, p)) + adj[b].append((a, p)) + + while pq: + p, node = heapq.heappop(pq) + p = p*-1 + if node in visited: continue + visited.add(node) + + if node==end: return p + + for nei, p2 in adj[node]: + if nei in visited: continue + heapq.heappush(pq, (-1*p*p2, nei)) + + return 0 \ No newline at end of file diff --git a/problems/peak-index-in-a-mountain-array.PY b/problems/python/peak-index-in-a-mountain-array.py old mode 100644 new mode 100755 similarity index 100% rename from problems/peak-index-in-a-mountain-array.PY rename to problems/python/peak-index-in-a-mountain-array.py diff --git a/problems/python/perfect-squares.py b/problems/python/perfect-squares.py new file mode 100755 index 0000000..8123dc9 --- /dev/null +++ b/problems/python/perfect-squares.py @@ -0,0 +1,39 @@ +#Recursive +class Solution(object): + def numSquares(self, n): + def helper(n): + if not n: return 0 + if n in history: return history[n] + + elements = [e**2 for e in range(2, int(n**0.5)+1)] + ans = n + + for e in reversed(elements): + count_of_element = int(n/e) + n_approximate = count_of_element*e + ans = min(ans, count_of_element+helper(n-n_approximate)) + + history[n] = ans + return history[n] + + history = {1:1, 2:2, 3:3} + return helper(n) + +#DP +class Solution(object): + def numSquares(self, N): + squares = [s**2 for s in range(2, int(N**0.5)+1)] + + dp = [n for n in xrange(N+1)] + + for n in xrange(N+1): + for square in squares: + if square>n: + break + elif square==n: + dp[n] = 1 + break + else: + dp[n] = min(dp[n], dp[square]+dp[n-square]) + return dp[N] + \ No newline at end of file diff --git a/problems/python/permutation-in-string.py b/problems/python/permutation-in-string.py new file mode 100755 index 0000000..b3d6a5c --- /dev/null +++ b/problems/python/permutation-in-string.py @@ -0,0 +1,32 @@ +""" +Time: O(N) +Space: O(N) + +`counter1` counts the char in `s1` +`counter2` counts the char is `s2[i:j+1]` + +Create a sliding window with length `len(s1)`, l. From i to j. +Move the sliding window (`s2[i:j+1]`) from left to right while maintaining counter2. +Each iteration, add s2[j] to the sliding window. +At the end of iteration, remove s2[i] from the sliding window. +If two counter are the same, s1 and s2[i:j+1] are permutations of each other. +""" +import collections + +class Solution(object): + def checkInclusion(self, s1, s2): + l = len(s1) + counter1 = collections.Counter(s1) + counter2 = collections.Counter(s2[:l-1]) + + for i in xrange(len(s2)): + j = i+l-1 + if j>=len(s2): break + counter2[s2[j]] += 1 + + if counter1==counter2: return True + + counter2[s2[i]] -= 1 + if counter2[s2[i]]==0: counter2.pop(s2[i], None) + + return False \ No newline at end of file diff --git a/problems/python/permutation-sequence.py b/problems/python/permutation-sequence.py new file mode 100755 index 0000000..f7e5be2 --- /dev/null +++ b/problems/python/permutation-sequence.py @@ -0,0 +1,14 @@ +class Solution(object): + def getPermutation(self, N, K): + K = K-1 #make it 0-index + nums = range(1, N+1) + ans = '' + + while N>0: + a = K/math.factorial(N-1) + ans += str(nums[a]) + nums.pop(a) + + K -= math.factorial(N-1)*(a+1) + N -= 1 + return ans \ No newline at end of file diff --git a/problems/python/permutations-ii.py b/problems/python/permutations-ii.py new file mode 100755 index 0000000..a6c9ff7 --- /dev/null +++ b/problems/python/permutations-ii.py @@ -0,0 +1,80 @@ +""" +This is the extended question of the [first one](https://leetcode.com/problems/permutations/submissions/). +The differece is there will be duplicates in the `nums`. + +```python +if i>0 and options[i]==options[i-1]: continue +``` +Above lets us skip exploring the same path. +We can directly skip the `i` if the value is the same with `i-1`, because `dfs()` on `i-1` has already cover up all the possiblities. + +The time complexity is `O(N!)`. +The space complexity is `O(N!)`, too. +""" +class Solution(object): + def permuteUnique(self, nums): + def dfs(path, options): + if len(path)==len(nums): + opt.append(path) + return + for i, n in enumerate(options): + if i>0 and options[i]==options[i-1]: continue + dfs(path+[n], options[:i]+options[i+1:]) + opt = [] + nums.sort() + dfs([], nums) + return opt + + +""" +Time: O(N!) +Space: O(N!) + +Since we are iterating the key of the counter, we will only place "each kind of number" at the first place once. +For example, [1,1,2], it will not happend that +We place the first "1" at index 0, and keep exploring... +And place the second "1" at index 0, and keep exploring... +""" +class Solution(object): + def permuteUnique(self, nums): + def helper(path): + if len(path)==len(nums): ans.append(path[:]) + + for num in counter: + if counter[num]>0: + path.append(num) + counter[num] -= 1 + + helper(path) + + path.pop() + counter[num] += 1 + ans = [] + counter = collections.Counter(nums) + helper([]) + return ans + + +""" +差板法 +""" +class Solution(object): + def permuteUnique(self, nums): + if not nums: return [] + + permutations = collections.deque([[nums[0]]]) + + for i in xrange(1, len(nums)): + num = nums[i] + l = len(permutations) + + while l: + permutation = permutations.popleft() + for j in xrange(len(permutation)+1): + if 0=N: ans.append(nums[:]) + + for j in xrange(i, N): + nums[i], nums[j] = nums[j], nums[i] + helper(i+1) + nums[i], nums[j] = nums[j], nums[i] + + N = len(nums) + ans = [] + helper(0) + return ans + + +""" +差板法 +""" +class Solution(object): + def permute(self, nums): + if not nums: return [] + + permutations = collections.deque([[nums[0]]]) + + for i in xrange(1, len(nums)): + num = nums[i] + l = len(permutations) + + while l: + permutation = permutations.popleft() + for j in xrange(len(permutation)+1): + newPermutaion = permutation[:] + newPermutaion.insert(j, num) + permutations.append(newPermutaion) + l -= 1 + + return permutations \ No newline at end of file diff --git a/problems/python/populating-next-right-pointers-in-each-node-ii.py b/problems/python/populating-next-right-pointers-in-each-node-ii.py new file mode 100755 index 0000000..718d84d --- /dev/null +++ b/problems/python/populating-next-right-pointers-in-each-node-ii.py @@ -0,0 +1,50 @@ +""" +Time: O(N) +Space: O(1) + +View each level as a link list. +Traverse the tree level by level. For each level: +Starting from the leftmost (the head of the link list), we establish the next pointer of the children. + +curr.left.next = curr.right or (curr.next.left or curr.next.right) or (curr.next.next.left or curr.next.next.right) or (...) +curr.right.next = (curr.next.left or curr.next.right) or (curr.next.next.left or curr.next.next.right) or (...) +""" +class Solution(object): + def connect(self, root): + if not root: return root + + leftmost = root + + while leftmost: + curr = leftmost + + while curr: + if curr.left: + if curr.right: + curr.left.next = curr.right + else: + n = curr.next + while n: + curr.left.next = n.left or n.right + if curr.left.next: break + n = n.next + + if curr.right: + n = curr.next + while n: + curr.right.next = n.left or n.right + if curr.right.next: break + n = n.next + + curr = curr.next + + nextLevelLeftmost = None + n = leftmost + while n: + if n.left or n.right: + nextLevelLeftmost = n.left or n.right + break + n = n.next + leftmost = nextLevelLeftmost + + return root \ No newline at end of file diff --git a/problems/python/populating-next-right-pointers-in-each-node.py b/problems/python/populating-next-right-pointers-in-each-node.py new file mode 100755 index 0000000..0f01cf2 --- /dev/null +++ b/problems/python/populating-next-right-pointers-in-each-node.py @@ -0,0 +1,56 @@ +""" +Time: O(N) +Space: O(N) + +Traverse the tree level by level. +For each level connect the node to the "next node". +""" +class Solution(object): + def connect(self, root): + if not root: return root + level = collections.deque([root]) + nextLevel = collections.deque() + + while level: + node = level.popleft() + + if node.left: nextLevel.append(node.left) + if node.right: nextLevel.append(node.right) + + if not level: + if not nextLevel: break + for i, c in enumerate(nextLevel): + if i==len(nextLevel)-1: continue #skip last + c.next = nextLevel[i+1] + level = nextLevel + nextLevel = collections.deque() + + return root + + +""" +Time: O(N) +Space: O(1) + +View each level as a link list. +Traverse the tree level by level. For each level: +Starting from the leftmost (the head of the link list), we establish the next pointer of the children. +curr.left.next = curr.right +curr.right.next = curr.next.left +""" +class Solution(object): + def connect(self, root): + if not root: return root + + leftmost = root + while leftmost: + + curr = leftmost + while curr: + if curr.left: curr.left.next = curr.right + if curr.right and curr.next: curr.right.next = curr.next.left + curr = curr.next + + leftmost = leftmost.left + + return root \ No newline at end of file diff --git a/problems/python/powx-n.py b/problems/python/powx-n.py new file mode 100755 index 0000000..d0fac2b --- /dev/null +++ b/problems/python/powx-n.py @@ -0,0 +1,12 @@ +class Solution(object): + def myPow(self, x, n): + if n==0: + return 1 + elif n<0: + return 1/self.myPow(x, -n) + elif n%2>0: + half = self.myPow(x, n-1) + return x*half + elif n%2==0: + half = self.myPow(x, n/2) + return half*half \ No newline at end of file diff --git a/problems/product-of-array-except-self.py b/problems/python/product-of-array-except-self.py old mode 100644 new mode 100755 similarity index 57% rename from problems/product-of-array-except-self.py rename to problems/python/product-of-array-except-self.py index 006e06f..76e8b48 --- a/problems/product-of-array-except-self.py +++ b/problems/python/product-of-array-except-self.py @@ -36,4 +36,49 @@ def productExceptSelf(self, nums): output[i] = output[i]*t #[5] t = t*nums[i] #[4] - return output \ No newline at end of file + return output + +""" +Time: O(N) +Space: O(N) + +left[i] := product of all the element left to nums[i] +right[i] := product of all the element right to nums[i] +""" +class Solution(object): + def productExceptSelf(self, nums): + N = len(nums) + + left = [1]*N + for i in xrange(1, len(nums)): + left[i] = left[i-1] * nums[i-1] + + right = [1]*N + for i in xrange(N-2, -1, -1): + right[i] = right[i+1] * nums[i+1] + + ans = [] + for i in xrange(N): + ans.append(left[i]*right[i]) + return ans + +""" +Time: O(N) +Space: O(1) +""" +class Solution(object): + def productExceptSelf(self, nums): + N = len(nums) + + #[1] + opt = [1]*N + for i in xrange(1, len(nums)): + opt[i] = opt[i-1] * nums[i-1] + + #[2] + t = 1 + for i in xrange(N-2, -1, -1): + t *= nums[i+1] + opt[i] *= t + + return opt \ No newline at end of file diff --git a/problems/python/profitable-schemes.py b/problems/python/profitable-schemes.py new file mode 100755 index 0000000..2c16d85 --- /dev/null +++ b/problems/python/profitable-schemes.py @@ -0,0 +1,45 @@ +""" +TLE +dp[i][n][p] := considering profit[:i], what is the number of ways produce profit p with n people. +""" +class Solution(object): + def profitableSchemes(self, maxMember, minProfit, group, profit): + P = sum(profit) + N = sum(group) + dp = [[[0 for _ in xrange(P+1)] for _ in xrange(N+1)] for _ in xrange(len(profit)+1)] + dp[0][0][0] = 1 + + count = 0 + for i in xrange(1, len(profit)+1): + for n in xrange(N+1): + for p in xrange(P+1): + dp[i][n][p] = (dp[i-1][n-group[i-1]][p-profit[i-1]] if p-profit[i-1]>=0 and n-group[i-1]>=0 else 0) + dp[i-1][n][p] + if i==len(profit) and p>=minProfit and n<=maxMember: count += dp[i][n][p] + return count + + + +""" +dp[i][g][p] := consider only crime[:i] the scheme that can generate profit p using man power g. +""" +class Solution(object): + def profitableSchemes(self, n, minProfit, group, profit): + N = len(profit) + + dp = [[[0]*(n+2) for _ in xrange(minProfit+1)] for _ in xrange(N+1)] + dp[0][0][0] = 1 + + for i in xrange(1, N+1): + for p in xrange(minProfit+1): + for g in xrange(n+1): + pi = profit[i-1] + gi = group[i-1] + + #considerting last round using p and g + dp[i][p][g] += dp[i-1][p][g] + dp[i][min(pi+p, minProfit)][min(gi+g, n+1)] += dp[i-1][p][g] + + ans = 0 + for g in xrange(n+1): + ans += dp[N][minProfit][g] + return ans % (10**9 + 7) \ No newline at end of file diff --git a/problems/python/queue-reconstruction-by-height.py b/problems/python/queue-reconstruction-by-height.py new file mode 100755 index 0000000..7c76e18 --- /dev/null +++ b/problems/python/queue-reconstruction-by-height.py @@ -0,0 +1,15 @@ +""" +Time: O(N^2) +Space: O(N) + +People need to be exactly insert to index k, so that there are "exact" k people equal or taller to k. +Shorter person does not matter (invisible) to taller person, so insert taller person first. +People does not care about the people on their right, so insert the person with smaller k first. +""" +class Solution(object): + def reconstructQueue(self, people): + people.sort(key=lambda x: (-x[0], x[1])) + ans = [] + for h, k in people: + ans.insert(k, (h, k)) + return ans \ No newline at end of file diff --git a/problems/python/random-pick-with-weight.py b/problems/python/random-pick-with-weight.py new file mode 100755 index 0000000..9c88bd0 --- /dev/null +++ b/problems/python/random-pick-with-weight.py @@ -0,0 +1,29 @@ +""" +For W = [1,3,2,4] (total 10, 1+2+3+4) +Think of a line _,___,__,____ ([1,4,6,10]) +Now we randomly throw a ball on the line the probability to land on the first section will be 1/10. +the second section, 3/10. +the third section, 2/10. +the forth section, 4/10. + +Above is equivilant to we randomly pick a number [0~10) and see which section it is in. +And since the cumulative probability in the "line" is strictly increasing, we can use binary search. + +Time: O(LogN), N is the number of W. +Space: O(N). +""" +from random import randrange + +class Solution(object): + + def __init__(self, W): + self.line = [] #cumulative probability distribution + self.total = 0 + + for w in W: + self.total += w + self.line.append(self.total) + + def pickIndex(self): + rand = random.randrange(self.total) + return bisect.bisect(self.line, rand) \ No newline at end of file diff --git a/problems/python/range-addition.py b/problems/python/range-addition.py new file mode 100755 index 0000000..9be6d1b --- /dev/null +++ b/problems/python/range-addition.py @@ -0,0 +1,59 @@ +""" +Time: O(N+K), N is the length. K is the updates count. +Space: O(N) + +The bottleneck of the naive solution is that each "update" takes O(N) time to traverse from s to e. +The to total time complexity will become O(NK). +So we need to find a way to summarize the updates and apply those at once... + +Declare updateSummary, I will store the all the updates in it. + +Given length equals to 10: [0,0,0,0,0,0,0,0,0,0] +For update (1,3,4), you can also see it as +[0,+4,+4,+4,+4,+4,+4,+4,+4,+4] (Add 4 to all element after and including) index 1) +[0,0,0,0,-4,-4,-4,-4,-4,-4] (Remove 4 to all element after index 3) +And we can summarize this in updateSummary as [0,4,0,0,-4,0,0,0,0,0] + +For update (0,2,-2), you can also see it as +[-2,-2,-2,-2,-2,-2,-2,-2,-2,-2] (Add -2 to all element after and including index 0) +[0,0,0,+2,+2,+2,+2,+2,+2,+2] (Remove -2 to all element after index 2) +And we can summarize this in updateSummary as [-2,0,0,+2,0,0,0,0,0,0] + +updateSummary[i] means adding value updateSummary[i] to all index after and including i in ans. +""" +class Solution(object): + def getModifiedArray(self, length, updates): + ans = [0]*length + updateSummary = [0]*length + + for s, e, v in updates: + updateSummary[s] += v + if e+1=low: total += helper(node.left, low, high) + return total + + return helper(root, low, high) \ No newline at end of file diff --git a/problems/python/range-sum-query-immutable.py b/problems/python/range-sum-query-immutable.py new file mode 100755 index 0000000..74a0e06 --- /dev/null +++ b/problems/python/range-sum-query-immutable.py @@ -0,0 +1,13 @@ +class NumArray(object): + def __init__(self, nums): + #total[i] = nums[0]+nums[1]+...+nums[i] + self.total = [] + + #initialize total + temp = 0 + for num in nums: + temp += num + self.total.append(temp) + + def sumRange(self, i, j): + return self.total[j] - self.total[i-1] if i>0 else self.total[j] \ No newline at end of file diff --git a/problems/python/range-sum-query-mutable.py b/problems/python/range-sum-query-mutable.py new file mode 100755 index 0000000..2502a15 --- /dev/null +++ b/problems/python/range-sum-query-mutable.py @@ -0,0 +1,67 @@ +""" +Time: O(LogN) for all operations. +Space: O(N) for the segment tree. + +Build a segment tree from the array `nums`. Each node store the info of the segment in nums (from `node.start` to `node.end`) +And `node.val` is equal to sum from nums[start] to nums[end] +""" +class NumArray(object): + + def __init__(self, nums): + def buildSegmentTree(start, end): + if start>end: return None + node = Node(start, end) + + if start==end: + node.val = nums[end] + else: + node.left = buildSegmentTree(start, node.mid) + node.right = buildSegmentTree(node.mid+1, end) + node.val = (node.left.val if node.left else 0) + (node.right.val if node.right else 0) + + return node + + self.root = buildSegmentTree(0, len(nums)-1) + + + def update(self, i, val): + def helper(node, i, val): + if node.start==node.end==i: + node.val = val + return + + if node.mid0 will be put back to h. [2] + +If heap is drained before we add k element to the ans. It is not possible to form ans. Return emptry string. [3] + +Time complexity: +Contruct counter takes O(N). +Contruct the heap (heapify) takes O(H), H is the number of element in the heap. +In the while loop, each char in s will be pop once. O(NLogH). +Since the problem said that the string will only have lower case letters. So H will at most be 26. +So O(N+H+NlogH) ~= O(N+26+NLog26) ~= O(N) +""" +class Solution(object): + def rearrangeString(self, s, k): + if k==0: return s + ans = '' + + #[0] + counter = collections.Counter(s) + h = [(-counter[c], c) for c in counter] + heapq.heapify(h) + + while h: + l = [] + for i in xrange(k): + _, c = heapq.heappop(h) + ans += c + counter[c] -= 1 + if counter[c]!=0: l.append((-counter[c], c)) #[2] + + if len(ans)==len(s): return ans + if not h and i!=k-1: return '' #[3] + + for e in l: heapq.heappush(h, e) + + return ans #this line should never be executed \ No newline at end of file diff --git a/problems/python/reconstruct-itinerary.py b/problems/python/reconstruct-itinerary.py new file mode 100755 index 0000000..2a07375 --- /dev/null +++ b/problems/python/reconstruct-itinerary.py @@ -0,0 +1,48 @@ +#build graph with departure city as key and (destination, ticket_id) as value +#G = build_graph() +#itinerary = ['JFK'] +#ticket_used = set() +#dfs() + #if len(itinerary)==len(tickets)+1: return True + #here = itinerary[-1] + #if here not in G: return False + #candidates = nei, ticket_id for nei, ticket_id in G[here] if ticket_id not in ticket_used + #for nei, ticket_id in candidates: + #ticket_used.add(ticket_id) + #itinerary.append(nei) + #if dfs(): return True + #ticket_used.remove(ticket_id) + #itinerary.pop() + #return False + +import collections + +class Solution(object): + def findItinerary(self, tickets): + def dfs(): + if len(itinerary)==len(tickets)+1: return True + here = itinerary[-1] + if here not in G: return False + + candidates = [(nei, ticket_id) for nei, ticket_id in G[here] if ticket_id not in ticket_used] + + for nei, ticket_id in candidates: + ticket_used.add(ticket_id) + itinerary.append(nei) + if dfs(): return True + ticket_used.remove(ticket_id) + itinerary.pop() + return False + + G = collections.defaultdict(list) + itinerary = ['JFK'] + ticket_used = set() + + #build graph + ticket_id_counter = 0 + for n1, n2 in tickets: + G[n1].append((n2, ticket_id_counter)) + ticket_id_counter += 1 + for k in G: G[k].sort(key=lambda x:x[0]) + + return dfs() \ No newline at end of file diff --git a/problems/python/recover-binary-search-tree.py b/problems/python/recover-binary-search-tree.py new file mode 100755 index 0000000..eb81e74 --- /dev/null +++ b/problems/python/recover-binary-search-tree.py @@ -0,0 +1,68 @@ +""" +Time: O(NLogN) +Space: O(N) +""" +class Solution(object): + def recoverTree(self, root): + memo = {} #{node.val:node} + stack = [] + node = root + vals = [] + + #inorder traversal and store the values in `vals` and `memo` + while node or stack: + while node: + stack.append(node) + node = node.left + + node = stack.pop() + + memo[node.val] = node + vals.append(node.val) + + node = node.right + + #find two val that needed to be swapped + diff = [] + sortedVals = sorted(vals) + for i in xrange(len(sortedVals)): + if vals[i]!=sortedVals[i]: diff.append(vals[i]) + if len(diff)>=2: break + + #swap the values + memo[diff[0]].val = diff[1] + memo[diff[1]].val = diff[0] + + return root + + +""" +Time: O(N) +Space: O(N) +""" +class Solution(object): + def recoverTree(self, root): + stack = [] + node = root + prev = TreeNode(float('-inf')) + swap1 = swap2 = None + + #inorder traversal and find the swapped values + while node or stack: + while node: + stack.append(node) + node = node.left + + node = stack.pop() + + if swap1==None and prev.val>node.val: swap1 = prev + if swap1!=None and prev.val>node.val: swap2 = node + + prev = node + + node = node.right + + #swap the values + swap1.val, swap2.val = swap2.val, swap1.val + + return root \ No newline at end of file diff --git a/problems/python/redundant-connection.py b/problems/python/redundant-connection.py new file mode 100755 index 0000000..fb938e2 --- /dev/null +++ b/problems/python/redundant-connection.py @@ -0,0 +1,95 @@ +#DFS +""" +For each edge (u, v), traverse the graph with a depth-first search to see if we can connect u to v. If we can, then it must be the duplicate edge. +Time Complexity: O(N^2) +Space Complexity: O(N) +""" +from collections import defaultdict + +class Solution(object): + def findRedundantConnection(self, edges): + def dfs(u, v): + seen = set() + stack = [] + stack.append(u) + + while stack: + node = stack.pop() + seen.add(node) + + if v in G[node]: return True + + for nei in G[node]: + if nei not in seen: + stack.append(nei) + return False + + G = defaultdict(set) + + for u, v in edges: + if u in G and v in G and dfs(u, v): return u, v + G[u].add(v) + G[v].add(u) + +#Disjoint Set Union +""" +For Disjoint Set Union, see +https://www.youtube.com/watch?v=ID00PMy0-vE +Time Complexity: O(N) +Space Complexity: O(N) +""" +class DSU(object): + def __init__(self): + self.parant = range(1001) + self.rank = [0]*1001 + + def find(self, x): + if self.parant[x]!=x: + self.parant[x] = self.find(self.parant[x]) + return self.parant[x] + + def union(self, x, y): + xr, yr = self.find(x), self.find(y) + if xr==yr: + return False + elif self.rank[xr]>self.rank[yr]: + self.parant[yr] = xr + self.rank[xr] += 1 + else: + self.parant[xr] = yr + self.rank[yr] += 1 + return True + +class Solution(object): + def findRedundantConnection(self, edges): + dsu = DSU() + for edge in edges: + if not dsu.union(*edge): + return edge + +#Disjoint Set Union without Ranking +""" +Time Complexity: O(N) +Space Complexity: O(N) +""" +class DSU(object): + def __init__(self): + self.parant = range(1001) + + def find(self, x): + if self.parant[x]!=x: + self.parant[x] = self.find(self.parant[x]) + return self.parant[x] + + def union(self, x, y): + xr, yr = self.find(x), self.find(y) + if xr==yr: return False + self.parant[yr] = xr + return True + +class Solution(object): + def findRedundantConnection(self, edges): + dsu = DSU() + for edge in edges: + if not dsu.union(*edge): + return edge \ No newline at end of file diff --git a/problems/python/remove-all-adjacent-duplicates-in-string.py b/problems/python/remove-all-adjacent-duplicates-in-string.py new file mode 100755 index 0000000..f4bb906 --- /dev/null +++ b/problems/python/remove-all-adjacent-duplicates-in-string.py @@ -0,0 +1,12 @@ +class Solution(object): + def removeDuplicates(self, s): + stack = [] + i = 0 + while i2: return False + if len(rowStrings)==1: return True + + s1 = rowStrings.pop() + s2 = rowStrings.pop() + + for i in xrange(len(s1)): + if (s1[i]=='0' and s2[i]=='1') or (s1[i]=='1' and s2[i]=='0'): continue + return False + return True \ No newline at end of file diff --git a/problems/python/remove-duplicates-from-sorted-array-ii.py b/problems/python/remove-duplicates-from-sorted-array-ii.py new file mode 100755 index 0000000..d7665be --- /dev/null +++ b/problems/python/remove-duplicates-from-sorted-array-ii.py @@ -0,0 +1,14 @@ +""" +j is the index to insert when a "new" number are found. +For each iteration, nums[:j] is the output result we currently have. +So nums[i] should check with nums[j-1] and nums[j-2]. +""" +class Solution(object): + def removeDuplicates(self, nums): + j = 2 + + for i in xrange(2, len(nums)): + if not (nums[i]==nums[j-1] and nums[i]==nums[j-2]): + nums[j] = nums[i] + j += 1 + return j \ No newline at end of file diff --git a/problems/python/remove-duplicates-from-sorted-array.py b/problems/python/remove-duplicates-from-sorted-array.py new file mode 100755 index 0000000..7fbef36 --- /dev/null +++ b/problems/python/remove-duplicates-from-sorted-array.py @@ -0,0 +1,12 @@ +""" +j is the index to insert when a new number are found. +j will never catch up i, so j will not mess up the check. +""" +class Solution(object): + def removeDuplicates(self, nums): + j = 1 + for i in xrange(1, len(nums)): + if nums[i]!=nums[i-1]: + nums[j] = nums[i] + j += 1 + return j \ No newline at end of file diff --git a/problems/remove-duplicates-from-sorted-list.py b/problems/python/remove-duplicates-from-sorted-list.py old mode 100644 new mode 100755 similarity index 100% rename from problems/remove-duplicates-from-sorted-list.py rename to problems/python/remove-duplicates-from-sorted-list.py diff --git a/problems/python/remove-element.py b/problems/python/remove-element.py new file mode 100755 index 0000000..52999e8 --- /dev/null +++ b/problems/python/remove-element.py @@ -0,0 +1,12 @@ +""" +j is the index to insert when a number not equal to val. +j will never catch up i, so j will not mess up the check. +""" +class Solution(object): + def removeElement(self, nums, val): + j = 0 + for n in nums: + if n!=val: + nums[j] = n + j += 1 + return j \ No newline at end of file diff --git a/problems/python/remove-invalid-parentheses.py b/problems/python/remove-invalid-parentheses.py new file mode 100755 index 0000000..0cb7695 --- /dev/null +++ b/problems/python/remove-invalid-parentheses.py @@ -0,0 +1,38 @@ +class Solution(object): + def removeInvalidParentheses(self, s): + def dfs(s, curr, i, count): + if count<0: return + if len(curr)>self.maxLen: return + + if i>=len(s): + if len(curr)==self.maxLen and count==0: + self.ans.append(curr) + return + + if s[i]!='(' and s[i]!=')': + dfs(s, curr+s[i], i+1, count) + elif not curr or s[i]!=curr[-1]: + dfs(s, curr+s[i], i+1, count + (1 if s[i]=='(' else -1)) + dfs(s, curr, i+1, count) + elif s[i]==curr[-1]: + dfs(s, curr+s[i], i+1, count + (1 if s[i]=='(' else -1)) + + def getMaxLen(s): + openCount = removeCount = 0 + for c in s: + if c=='(': + openCount += 1 + elif c==')': + openCount -= 1 + + if openCount<0: + removeCount += abs(openCount) + openCount = 0 + removeCount += openCount + return len(s)-removeCount + + self.ans = [] + self.maxLen = getMaxLen(s) + + dfs(s, "", 0, 0) + return self.ans \ No newline at end of file diff --git a/problems/remove-linked-list-elements.py b/problems/python/remove-linked-list-elements.py old mode 100644 new mode 100755 similarity index 100% rename from problems/remove-linked-list-elements.py rename to problems/python/remove-linked-list-elements.py diff --git a/problems/python/remove-nth-node-from-end-of-list.py b/problems/python/remove-nth-node-from-end-of-list.py new file mode 100755 index 0000000..d92ddd7 --- /dev/null +++ b/problems/python/remove-nth-node-from-end-of-list.py @@ -0,0 +1,25 @@ +class Solution(object): + def removeNthFromEnd(self, head, n): + def getCount(node0): + curr = node0 + count = 0 + while curr: + curr = curr.next + count += 1 + return count + + def removeNext(node0): + nextNode = node0.next + if not nextNode: return + node0.next = nextNode.next + + k = getCount(head)-n-1 #need to "curr = curr.next" k times to reach the node that we can call removeNext(curr) + if k==-1: return head.next #remove head + + curr = head + while k>0: + k -= 1 + curr = curr.next + + removeNext(curr) + return head \ No newline at end of file diff --git a/problems/reorder-list.py b/problems/python/reorder-list.py old mode 100644 new mode 100755 similarity index 100% rename from problems/reorder-list.py rename to problems/python/reorder-list.py diff --git a/problems/python/repeated-string-match.py b/problems/python/repeated-string-match.py new file mode 100755 index 0000000..95c1f66 --- /dev/null +++ b/problems/python/repeated-string-match.py @@ -0,0 +1,32 @@ +class Solution(object): + def repeatedStringMatch(self, a, b): + """ + compare a[i] and b[j] + if a[i]!=b[j], return -1 + if a new set of "a" is needed, count += 1 + """ + def helper(i): + count = 1 + j = 0 + + while j0: return -1 + + #the index of the starting point need to be as small as posible. So that we can get the minimum answer. + for i in xrange(len(a)): + if a[i]==b[0]: + ans = helper(i) + if ans>0: return ans + + return -1 \ No newline at end of file diff --git a/problems/python/replace-the-substring-for-balanced-string.py b/problems/python/replace-the-substring-for-balanced-string.py new file mode 100755 index 0000000..0ce1250 --- /dev/null +++ b/problems/python/replace-the-substring-for-balanced-string.py @@ -0,0 +1,15 @@ +class Solution(object): + def balancedString(self, s): + n = len(s) + counter = collections.Counter(s) + i = 0 + ans = n + + for j, c in enumerate(s): + counter[c] -= 1 + + while ienvelopes[j][0] and envelopes[i][1]>envelopes[j][1]: + dp[i] = max(dp[i], dp[j]+1) + ans = max(ans, dp[i]) + + return ans + +""" +dp[i] := the smallest ending number of a sequence that has length i+1 +First, sort the envelopes by width. (envelope[0]) +Second, return the LIS of heights ([envelope[1] for envelope in envelopes]). +This automatically gets the number of "russian dolls". + +However, we might choose [3,4], [3,7] as a valid solution. This is not correct because w1==w2. +So we do a quick fix by sorting the envelopes with w and h reversed. +So that getLIS() will not choose [3,4], [3,7] + +Time: O(NLogN) +Space: O(N) +""" +import bisect +class Solution(object): + def maxEnvelopes(self, envelopes): + if not envelopes: return 0 + + N = len(envelopes) + dp = [1]*N + envelopes = envelopes.sort(key=lambda x:(x[0], -x[1])) + + return self.getLIS([envelope[1] for envelope in envelopes]) + + def getLIS(self, A): + dp = [] + + for n in A: + i = bisect.bisect_left(dp, n) + + if i==len(dp): + dp.append(n) + else: + dp[i] = n + + return len(dp) diff --git a/problems/python/same-tree.py b/problems/python/same-tree.py new file mode 100755 index 0000000..57401db --- /dev/null +++ b/problems/python/same-tree.py @@ -0,0 +1,49 @@ +from collections import deque + +class Solution(object): + def isSameTree(self, p, q): + q1 = deque([p]) + q2 = deque([q]) + while q1 or q2: + if not q1 or not q2: return False + n1 = q1.popleft() + n2 = q2.popleft() + + if n1 and n2: + if n1.val!=n2.val: return False + q1.append(n1.left) + q1.append(n1.right) + q2.append(n2.left) + q2.append(n2.right) + elif n1 and not n2: + return False + elif not n1 and n2: + return False + return True + + +""" +Time: O(N) +Space: O(N) + +Use BFS to traverse the tree and represent the result as a string. +Note that null node still need to be shown in the string. +Because there are the same val in the tree, and different tree may result in the same string. +""" +class Solution(object): + def isSameTree(self, p, q): + def getStr(node): + s = '' + q = collections.deque([node]) + + while q: + node = q.popleft() + if not node: + s += '#' + else: + s += str(node.val) + q.append(node.left) + q.append(node.right) + return s + + return getStr(p)==getStr(q) \ No newline at end of file diff --git a/problems/python/satisfiability-of-equality-equations.py b/problems/python/satisfiability-of-equality-equations.py new file mode 100755 index 0000000..2c02d16 --- /dev/null +++ b/problems/python/satisfiability-of-equality-equations.py @@ -0,0 +1,57 @@ +from collections import defaultdict + +class Solution(object): + def equationsPossible(self, equations): + def isConnected(n1, n2): + if n1==n2: return True + + stack = [n1] + visited = set() + + while stack: + n = stack.pop() + if n in visited: continue + visited.add(n) + + if n==n2: return True + + stack.extend(graph[n]) + return False + + not_equals = set() + graph = defaultdict(list) + + #build graph + for e in equations: + if e[1:3]=='==': + graph[e[0]].append(e[3]) + graph[e[3]].append(e[0]) + elif e[1:3]=='!=': + if (e[0], e[3]) not in not_equals and (e[3], e[0]) not in not_equals: + not_equals.add((e[0], e[3])) + + #find contradiction + for n1, n2 in not_equals: + if isConnected(n1, n2): return False + + return True + +""" +For each `!=` equation, if we find two variables are equal (`isConnected()`), we return `False`. +Otherwise, we return `True`. + +[build graph] +First, lets build the graph. +Variables in the same graph means they are all equal. + +[find contradiction] +Second, we find if any "!=" variables (`n1` and `n2`) exist in the same graph. +Starting from `n1`, if we can find `n2` through DFS, they have the same value. + +Time: O(EV). +Building the graph takes O(E). Finding contradiction takes O(EV). +E is the number of edges, in this case, `len(equations)`. +V is the number of nodes, in this case, the unique variables in the equations. + +Space: O(V). For the graph. +""" \ No newline at end of file diff --git a/problems/score-of-parentheses.py b/problems/python/score-of-parentheses.py old mode 100644 new mode 100755 similarity index 100% rename from problems/score-of-parentheses.py rename to problems/python/score-of-parentheses.py diff --git a/problems/search-a-2d-matrix.py b/problems/python/search-a-2d-matrix.py old mode 100644 new mode 100755 similarity index 100% rename from problems/search-a-2d-matrix.py rename to problems/python/search-a-2d-matrix.py diff --git a/problems/python/search-in-a-binary-search-tree.py b/problems/python/search-in-a-binary-search-tree.py new file mode 100755 index 0000000..08f05d2 --- /dev/null +++ b/problems/python/search-in-a-binary-search-tree.py @@ -0,0 +1,31 @@ +""" +Time complexity: O(LogN) +Space complexity: O(1) +""" +class Solution(object): + def searchBST(self, root, val): + node = root + + while node: + if node.val==val: + return node + elif node.val>val: + node = node.left + else: + node = node.right + + return None + +""" +Time complexity: O(LogN) +Space complexity: O(LogN) +""" +class Solution(object): + def searchBST(self, node, val): + if not node: return None + if node.val==val: + return node + elif node.valval: + return self.searchBST(node.left, val) \ No newline at end of file diff --git a/problems/search-in-rotated-sorted-array-ii.py b/problems/python/search-in-rotated-sorted-array-ii.py old mode 100644 new mode 100755 similarity index 60% rename from problems/search-in-rotated-sorted-array-ii.py rename to problems/python/search-in-rotated-sorted-array-ii.py index 43e913b..678426f --- a/problems/search-in-rotated-sorted-array-ii.py +++ b/problems/python/search-in-rotated-sorted-array-ii.py @@ -69,4 +69,62 @@ def helper(l, r): return False if not nums: return False - return helper(0, len(nums)-1) \ No newline at end of file + return helper(0, len(nums)-1) + + +""" +Time: O(LogN). Worse Case: O(N). +Space: O(1) + +The key idea for most rotated array question is that +If you cut a rotated array into half, +One half will be in-order. +One half will be rotated or in-order. +""" +class Solution(object): + def search(self, A, T): + N = len(A) + l = 0 + r = N-1 + + while l<=r: + + #skip repeated numbers + while r>0 and A[r]==A[r-1]: r -= 1 + while l0 and A[l]==A[r]: r -= 1 + while lA[r]: return False #out of range l~r + + if A[m]word: + r = m-1 + else: + l = m+1 + + return [] + + def getTop3(products, word, i): + suggestion = [] + + while i-1>=0 and products[i-1].startswith(word): + i -= 1 + + for j in xrange(i, min(len(products), i+3)): + if not products[j].startswith(word): break + suggestion.append(products[j]) + return suggestion + + + ans = [] + products.sort() + + currSearchWord = '' + for c in searchWord: + currSearchWord += c + ans.append(search(products, currSearchWord)) + + return ans + + + +""" +Time: O(M) + O(S^2) ~= O(M). M is the number of char in products. S is number of char of a searchWord. +O(M) to build trie. O(len(prefix)) (~= O(S/2)) to get to the prefix node, and it will perform S time. O(1) to get top 3. +Space: O(M) + +Use Trie to store the relation and search it. +""" +class Node(object): + def __init__(self, c): + self.c = c + self.nexts = {} + + +class Solution(object): + def suggestedProducts(self, products, searchWord): + ans = [] + root = Node('') + + #build trie + for product in products: + curr = root + for c in product+'.': + if c not in curr.nexts: + curr.nexts[c] = Node(c) + curr = curr.nexts[c] + + # search top3 for each prefix + curr = root + for i, c in enumerate(searchWord): + if c not in curr.nexts: break + curr = curr.nexts[c] + ans.append(self.getTop3(searchWord[:i+1], curr)) + + ans += [[] for _ in xrange(len(searchWord)-len(ans))] + return ans + + def getTop3(self, prefix, node): + def helper(prefix, node): + if len(top3)>=3: return + if '.' in node.nexts: top3.append(prefix) + + for c in 'abcdefghijklmnopqrstuvwxyz': + if c in node.nexts: + helper(prefix+c, node.nexts[c]) + + top3 = [] + helper(prefix, node) + return top3 + diff --git a/problems/second-highest-salary.sql b/problems/python/second-highest-salary.sql old mode 100644 new mode 100755 similarity index 100% rename from problems/second-highest-salary.sql rename to problems/python/second-highest-salary.sql diff --git a/problems/python/sell-diminishing-valued-colored-balls.py b/problems/python/sell-diminishing-valued-colored-balls.py new file mode 100755 index 0000000..e871597 --- /dev/null +++ b/problems/python/sell-diminishing-valued-colored-balls.py @@ -0,0 +1,25 @@ +class Solution(object): + def maxProfit(self, I, orders): + I.sort(reverse=True) + I.append(0) + + profit = 0 + w = 1 + + for i in xrange(len(I)-1): + if orders==0: break + if I[i]>I[i+1]: + if w*(I[i]-I[i+1])=len(data): continue + if data[i]!='#': + node.right = TreeNode(data[i]) + q.append(node.right) + i += 1 + + return root + + + +#Preorder traversal +class Codec: + def serialize(self, root): + if not root: return '#,' + return str(root.val) + ',' + self.serialize(root.left) + self.serialize(root.right) + + def deserialize(self, data): + data = data.split(',')[::-1] + return self.getNode(data) + + def getNode(self, data): + if not data: return None + val = data.pop() + if val=='#': return None + node = TreeNode(val) + node.left = self.getNode(data) + node.right = self.getNode(data) + return node \ No newline at end of file diff --git a/problems/serialize-and-deserialize-bst.py b/problems/python/serialize-and-deserialize-bst.py old mode 100644 new mode 100755 similarity index 100% rename from problems/serialize-and-deserialize-bst.py rename to problems/python/serialize-and-deserialize-bst.py diff --git a/problems/python/set-matrix-zeroes.py b/problems/python/set-matrix-zeroes.py new file mode 100755 index 0000000..94e512d --- /dev/null +++ b/problems/python/set-matrix-zeroes.py @@ -0,0 +1,35 @@ +""" +Space: O(1) +Time: O(N^2) + +Mark matrix[i][0] as string if all matrix[i][?] needs to be set to 0. +Mark matrix[0][j] as string if all matrix[?][j] needs to be set to 0. +There is an edje case where matrix[0][0] cannot represent for row and col at the same time. +So we need an additional variable, firstRowToZero. + +Explanation: https://www.youtube.com/watch?v=T41rL0L3Pnw +""" +class Solution(object): + def setZeroes(self, matrix): + firstRowToZero = False + + for i in xrange(len(matrix)): + for j in xrange(len(matrix[0])): + if matrix[i][j]==0: + if i!=0: + matrix[i][0] = str(matrix[i][0]) + else: + firstRowToZero = True + matrix[0][j] = str(matrix[0][j]) + + + for i in xrange(1, len(matrix)): + if type(matrix[i][0])!=type(''): continue + for j in xrange(len(matrix[0])): matrix[i][j] = 0 + + for j in xrange(len(matrix[0])): + if type(matrix[0][j])!=type(''): continue + for i in xrange(len(matrix)): matrix[i][j] = 0 + + if firstRowToZero: + for j in xrange(len(matrix[0])): matrix[0][j] = 0 \ No newline at end of file diff --git a/problems/shortest-bridge.py b/problems/python/shortest-bridge.py old mode 100644 new mode 100755 similarity index 100% rename from problems/shortest-bridge.py rename to problems/python/shortest-bridge.py diff --git a/problems/python/shortest-common-supersequence.py b/problems/python/shortest-common-supersequence.py new file mode 100755 index 0000000..1b4e582 --- /dev/null +++ b/problems/python/shortest-common-supersequence.py @@ -0,0 +1,41 @@ +""" +dp[i][j] := length of the scs of s1[:i] and s2[:j] +""" +class Solution(object): + def shortestCommonSupersequence(self, s1, s2): + N, M = len(s1), len(s2) + + if not s1: return s2 + if not s2: return s1 + + dp = [[0 for _ in xrange(M+1)] for _ in xrange(N+1)] + for i in range(1, N+1): dp[i][0] = i + for j in range(1, M+1): dp[0][j] = j + + for i in xrange(1, N+1): + for j in xrange(1, M+1): + if s1[i-1]==s2[j-1]: + dp[i][j] = dp[i-1][j-1]+1 + else: + dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1) + + i, j = N, M + ans = '' + + while i>0 and j>0: + if s1[i-1]==s2[j-1]: + ans = s1[i-1] + ans + i -= 1 + j -= 1 + else: + if dp[i][j-1]0: ans = s1[:i] + ans + if j>0: ans = s2[:j] + ans + + return ans \ No newline at end of file diff --git a/problems/python/shortest-distance-from-all-buildings.py b/problems/python/shortest-distance-from-all-buildings.py new file mode 100755 index 0000000..490e6a2 --- /dev/null +++ b/problems/python/shortest-distance-from-all-buildings.py @@ -0,0 +1,39 @@ +class Solution(object): + def shortestDistance(self, grid): + def bfs(i0, j0): + q = collections.deque([(i0, j0, 0)]) + visited = set() + while q: + i, j, dis = q.popleft() + if (i, j) in visited: continue + visited.add((i, j)) + + reach[i][j] += 1 + distances[i][j] += dis + + for iNext, jNext in ((i+1, j), (i-1, j), (i, j+1), (i, j-1)): + if not (0<=iNext=N or i<0 or j>=M or j<0: continue + if grid[i][j]==1 and k0>0: + qNext.append((step+1, i, j, k0-1)) + elif grid[i][j]==0: + qNext.append((step+1, i, j, k0)) + if not q: q = qNext + + return -1 \ No newline at end of file diff --git a/problems/python/shortest-path-in-binary-matrix.py b/problems/python/shortest-path-in-binary-matrix.py new file mode 100755 index 0000000..6811948 --- /dev/null +++ b/problems/python/shortest-path-in-binary-matrix.py @@ -0,0 +1,17 @@ +class Solution(object): + def shortestPathBinaryMatrix(self, grid): + q = collections.deque([(0, 0, 1)]) + seen = 2 + + while q: + i, j, step = q.popleft() + if not (0<=i0: + self.curr = self.curr.next + rand -= 1 + + #add self.curr.next's value to ans + ans.append(self.curr.next.val) + + #add self.curr.next to a new LinkList + if not curr: + head = self.curr.next + curr = head + else: + curr.next = self.curr.next + curr = curr.next + + #remove self.curr.next from the LinkList + self.curr.next = self.curr.next.next + + curr.next = head + self.curr = head + return ans + + def getLinkList(self, nums): + head = ListNode(nums[0]) + curr = head + for i in xrange(1, len(self.nums)): + curr.next = ListNode(nums[i]) + curr = curr.next + curr.next = head + return head \ No newline at end of file diff --git a/problems/python/simplify-path.py b/problems/python/simplify-path.py new file mode 100755 index 0000000..46589cf --- /dev/null +++ b/problems/python/simplify-path.py @@ -0,0 +1,18 @@ +class Solution(object): + def simplifyPath(self, path): + ans = '' + skip = 0 + + for directory in reversed(path.split('/')): + if directory=='' or directory=='.': + continue + elif directory=='..': + skip += 1 + else: + if skip>0: + skip -= 1 + continue + else: + ans = '/'+directory+ans + + return ans if ans!='' else '/' \ No newline at end of file diff --git a/problems/python/single-threaded-cpu.py b/problems/python/single-threaded-cpu.py new file mode 100755 index 0000000..fe30e21 --- /dev/null +++ b/problems/python/single-threaded-cpu.py @@ -0,0 +1,22 @@ +class Solution(object): + def getOrder(self, tasks): + ans = [] + tasks = sorted([(task[0], task[1], i) for i, task in enumerate(tasks)], reverse=True) + pq = [] #tasks available + now = 0 + + + while tasks or pq: + #check if the task is availiable, if yes, add to pq + while tasks and tasks[-1][0]<=now: + startTime, processTime, i = tasks.pop() + heapq.heappush(pq, (processTime, i)) + + if pq: + processTime, i = heapq.heappop(pq) + ans.append(i) + now += processTime + else: + now = tasks[-1][0] + + return ans \ No newline at end of file diff --git a/problems/python/sliding-window-maximum.py b/problems/python/sliding-window-maximum.py new file mode 100755 index 0000000..34098d8 --- /dev/null +++ b/problems/python/sliding-window-maximum.py @@ -0,0 +1,79 @@ +class Solution(object): + def maxSlidingWindow(self, nums, k): + opt = [] + q = collections.deque() + for i in xrange(len(nums)): + n = nums[i] + + #move the window + if q and q[0]<=i-k: q.popleft() + + #pop the right if the element in queue is not greater than the in-coming one + #by doing this, we can always keep the max in the current window at left most + while q and nums[q[-1]]<=n: q.pop() + + q.append(i) + + #add the max to the output array after the first kth element + if 1+i>=k: opt.append(nums[q[0]]) + return opt + + +""" +Keep the element in sliding window in a heap, so we can get the max easily. +But we do not know whether which element in the heap is out of the window. +So we use a counter to track the count of the elements inside the window. + +Time: O(NLogK), the `heapq.heappop(h)` in the while-loop in the for-loop will only be execute once for each element. +Space: O(N) +""" +class Solution(object): + def maxSlidingWindow(self, nums, k): + if not nums or not k: return [] + if k==1: return nums + + ans = [] + + counter = collections.Counter(nums[:k]) + h = [-num for num in nums[:k]] + heapq.heapify(h) + + for i in xrange(len(nums)): + while counter[-h[0]]==0: heapq.heappop(h) + ans.append(-h[0]) + + if i+k>=len(nums): break + counter[nums[i]] -= 1 + heapq.heappush(h, -nums[i+k]) + counter[nums[i+k]] += 1 + + return ans + +""" +Use deque the slide the window through nums. +The deque will at most contain k elements. +Each time add the max element to ans. + +Time: O(N), the `q.pop()` in the while-loop in the for-loop will only execute once for each element. +Space: O(N) +""" +class Solution(object): + def maxSlidingWindow(self, nums, k): + ans = [] + q = collections.deque() + + for i, num in enumerate(nums): + #keep only the element in the window inside deque. + #the window lies between index i-k+1 to i. + if q and q[0][1]<=i-k: q.popleft() + + #keep the element in deque in descending order by popping the elements from rihgt that are smaller than num. + #by keeping the deque in descending order, we can get the largest element by just getting the first element. + #also since they are certainly not the max in the deque, we don't care. + while q and q[-1][0]<=num: q.pop() + q.append((num, i)) + + #wait until i>=k-1 when the window pass at least k elements. + if (i>=k-1): ans.append(q[0][0]) + + return ans \ No newline at end of file diff --git a/problems/python/snapshot-array.py b/problems/python/snapshot-array.py new file mode 100755 index 0000000..baa9f15 --- /dev/null +++ b/problems/python/snapshot-array.py @@ -0,0 +1,19 @@ +class SnapshotArray(object): + + def __init__(self, length): + self.data = [[(-1, 0)] for _ in xrange(length)] + self.snapId = 0 + + + def set(self, index, val): + self.data[index].append((self.snapId, val)) + + + def snap(self): + self.snapId += 1 + return self.snapId - 1 + + + def get(self, index, snapId): + j = bisect.bisect_right(self.data[index], (snapId, float('inf'))) - 1 + return self.data[index][j][1] \ No newline at end of file diff --git a/problems/python/sort-colors.py b/problems/python/sort-colors.py new file mode 100755 index 0000000..b141228 --- /dev/null +++ b/problems/python/sort-colors.py @@ -0,0 +1,45 @@ +class Solution(object): + def sortColors(self, nums): + l = 0 + r = len(nums)-1 + + i = 0 + while i<=r: + if nums[i]==0: + nums[l], nums[i] = nums[i], nums[l] + l += 1 + i += 1 + elif nums[i]==2: + nums[i], nums[r] = nums[r], nums[i] + r -= 1 + elif nums[i]==1: + i += 1 + +# counting sort +class Solution(object): + def sortColors(self, nums): + count0 = count1 = count2 = 0 + + for num in nums: + if num==0: + count0 += 1 + elif num==1: + count1 += 1 + elif num==2: + count2 += 1 + + i = 0 + while count0>0: + nums[i] = 0 + i += 1 + count0 -= 1 + + while count1>0: + nums[i] = 1 + i += 1 + count1 -= 1 + + while count2>0: + nums[i] = 2 + i += 1 + count2 -= 1 \ No newline at end of file diff --git a/problems/sort-list.py b/problems/python/sort-list.py old mode 100644 new mode 100755 similarity index 100% rename from problems/sort-list.py rename to problems/python/sort-list.py diff --git a/problems/python/spiral-matrix.py b/problems/python/spiral-matrix.py new file mode 100755 index 0000000..96965d2 --- /dev/null +++ b/problems/python/spiral-matrix.py @@ -0,0 +1,42 @@ +class Solution(object): + def spiralOrder(self, matrix): + ans = [] + i, j = 0, 0 + direction = 'right' + count = 0 + + while count<=len(matrix)*len(matrix[0]): + iNext, jNext = i, j + + if direction=='right': + if j+1=0 and matrix[i][j-1]!='v': + jNext = j-1 + else: + direction = 'up' + elif direction=='up': + if i-1>=0 and matrix[i-1][j]!='v': + iNext = i-1 + else: + direction = 'right' + + if (iNext, jNext)!=(i, j) or count>=len(matrix)*len(matrix[0])-1: + ans.append(matrix[i][j]) + matrix[i][j] = 'v' #visited + count += 1 + i = iNext + j = jNext + + return ans[:-1] + + + \ No newline at end of file diff --git a/problems/split-array-into-fibonacci-sequence.py b/problems/python/split-array-into-fibonacci-sequence.py old mode 100644 new mode 100755 similarity index 100% rename from problems/split-array-into-fibonacci-sequence.py rename to problems/python/split-array-into-fibonacci-sequence.py diff --git a/problems/python/split-array-largest-sum.py b/problems/python/split-array-largest-sum.py new file mode 100755 index 0000000..3239503 --- /dev/null +++ b/problems/python/split-array-largest-sum.py @@ -0,0 +1,45 @@ +""" +TLE +dp[i][k] := min of the largest sum among k subarrays from nums[:i] +""" +class Solution(object): + def splitArray(self, nums, K): + N = len(nums) + + dp = [[float('inf') for _ in xrange(K+1)] for _ in xrange(N+1)] + dp[0][0] = 0 + + for i in xrange(1, N+1): + for k in xrange(1, min(i, K)+1): + for j in xrange(k, i+1): + dp[i][k] = min(dp[i][k], max(dp[j-1][k-1], sum(nums[j-1:i]))) + + return dp[N][K] + + +""" +TLE +dp[i][k] := min of the largest sum among k subarrays from nums[:i] +s[i][j] := sum of nums[i:j+1] +""" +class Solution(object): + def splitArray(self, nums, K): + N = len(nums) + + s = [[0 for _ in xrange(N)] for _ in xrange(N)] + for i in xrange(N): s[i][i] = nums[i] + for l in xrange(2, N): + for i in xrange(N): + j = i+l-1 + if j>=N: continue + s[i][j] = s[i+1][j-1]+nums[i]+nums[j] + + dp = [[float('inf') for _ in xrange(K+1)] for _ in xrange(N+1)] + dp[0][0] = 0 + + for i in xrange(1, N+1): + for k in xrange(1, min(i, K)+1): + for j in xrange(k, i+1): + dp[i][k] = min(dp[i][k], max(dp[j-1][k-1], s[j-1][i-1])) + + return dp[N][K] \ No newline at end of file diff --git a/problems/sqrtx.py b/problems/python/sqrtx.py old mode 100644 new mode 100755 similarity index 100% rename from problems/sqrtx.py rename to problems/python/sqrtx.py diff --git a/problems/squares-of-a-sorted-array.py b/problems/python/squares-of-a-sorted-array.py old mode 100644 new mode 100755 similarity index 100% rename from problems/squares-of-a-sorted-array.py rename to problems/python/squares-of-a-sorted-array.py diff --git a/problems/python/step-by-step-directions-from-a-binary-tree-node-to-another.py b/problems/python/step-by-step-directions-from-a-binary-tree-node-to-another.py new file mode 100755 index 0000000..a9177d4 --- /dev/null +++ b/problems/python/step-by-step-directions-from-a-binary-tree-node-to-another.py @@ -0,0 +1,36 @@ +class Solution(object): + def __init__(self): + self.lca = None + + def getDirections(self, root, startValue, destValue): + def findPath(node, path, val): + if not node: return False + if node.val==val: return True + + path.append('L') + if findPath(node.left, path, val): return True + path.pop() + + path.append('R') + if findPath(node.right, path, val): return True + path.pop() + + return False + + def findCount(node, startValue, destValue): + if not node: return 0 + count = 0 + if node.val==startValue or node.val==destValue: count += 1 + count += findCount(node.left, startValue, destValue) + count += findCount(node.right, startValue, destValue) + if count>=2 and not self.lca: self.lca = node + return count + + findCount(root, startValue, destValue) + + path1 = [] + findPath(self.lca, path1, startValue) + path2 = [] + findPath(self.lca, path2, destValue) + + return 'U'*len(path1) + ''.join(path2) \ No newline at end of file diff --git a/problems/python/stock-price-fluctuation.py b/problems/python/stock-price-fluctuation.py new file mode 100755 index 0000000..e75beb2 --- /dev/null +++ b/problems/python/stock-price-fluctuation.py @@ -0,0 +1,37 @@ +""" +[0] priceToTime tracks price to timestamps, but since for each timestamp the price might be overridden, the price in priceToTime might not be exsit. So we need to check if the price still have any valid timestamps. +[1] SortedDict is a BST like structure. + +Time: O(LogN) +Space: O(N) +""" +from sortedcontainers import SortedDict #[1] + +class StockPrice(object): + + def __init__(self): + self.timeToPrice = SortedDict() #time will be sorted + self.priceToTime = SortedDict() #the price will be sorted + + + def update(self, timestamp, price): + if timestamp in self.timeToPrice: + prevPrice = self.timeToPrice[timestamp] + self.priceToTime[prevPrice].remove(timestamp) + if len(self.priceToTime[prevPrice])==0: self.priceToTime.pop(prevPrice) #[0] + + if price not in self.priceToTime: self.priceToTime[price] = set() #initialized + self.priceToTime[price].add(timestamp) + self.timeToPrice[timestamp] = price + + + def current(self): + return self.timeToPrice.peekitem(-1)[1] + + + def maximum(self): + return self.priceToTime.peekitem(-1)[0] + + + def minimum(self): + return self.priceToTime.peekitem(0)[0] \ No newline at end of file diff --git a/problems/python/stone-game-ii.py b/problems/python/stone-game-ii.py new file mode 100755 index 0000000..1674449 --- /dev/null +++ b/problems/python/stone-game-ii.py @@ -0,0 +1,22 @@ +class Solution(object): + def stoneGameII(self, piles): + def helper(start, m): + if (start, m) in history: return history[(start, m)] + + if start>=len(piles): return 0 + if start+m*2>=len(piles): return sum(piles[start:]) + + max_stones = float('-inf') + for x in xrange(1, m*2+1): + max_stones = max(max_stones, sum(piles[start:])-helper(start+x, max(m, x))) + + history[(start, m)] = max_stones + return history[(start, m)] + + history = {} + return helper(0, 1) + +""" +helper(start, m) := the max stones that the first player will get with given piles[start:] and M. +max_stones = MAX{ (sum of all the stones) - (max_stones the other player will get) } = MAX{ sum(piles[start:]) - helper(start+x, max(m, x)) } +""" \ No newline at end of file diff --git a/problems/python/student-attendance-record-ii.py b/problems/python/student-attendance-record-ii.py new file mode 100755 index 0000000..efc2923 --- /dev/null +++ b/problems/python/student-attendance-record-ii.py @@ -0,0 +1,27 @@ +class Solution(object): + def checkRecord(self, n): + """ + dp[l] := number of eligible combination of a length l record without A + """ + + ans = 0 + M = 1000000007 + + dp = [0]*max(n+1, 4) + dp[0] = 1 + dp[1] = 2 + dp[2] = 4 + dp[3] = 7 + + for i in xrange(4, n+1): + dp[i] += dp[i-1]%M #ends at P + dp[i] += (dp[i-1]%M - dp[i-4]%M) #ends at L. All posiblity but the end cannot be PLL + + ans += dp[n] + + for i in xrange(n): + ans += dp[i] * dp[n-i-1] + ans %= M + + return ans + \ No newline at end of file diff --git a/problems/python/subarray-sum-equals-k.py b/problems/python/subarray-sum-equals-k.py new file mode 100755 index 0000000..0158b04 --- /dev/null +++ b/problems/python/subarray-sum-equals-k.py @@ -0,0 +1,54 @@ +""" +Time: O(N^2), this will get TLE +Space: O(N) + +prefixSum[i] == sum(nums[:i]) (nums[0] + nums[1] + ... + nums[i-1]) +prefixSum[j]-prefixSum[i] == sum(nums[i:j]) (nums[i] + nums[i+1] + ... + nums[j-1]) +By iterating through all i and j possibilities, we get all the subarray sum that equals to k. +""" +class Solution(object): + def subarraySum(self, nums, k): + N = len(nums) + prefixSum = [0] + ans = 0 + + temp = 0 + for n in nums: + temp += n + prefixSum.append(temp) + + for i in xrange(N+1): + for j in xrange(i+1, N+1): + if prefixSum[j]-prefixSum[i]==k: ans += 1 + + return ans + + +""" +Time: O(N) +Space: O(N) + +Let's optimize from above solution. +Let prefixSum[j] as J. +Let prefixSum[i] as I. + +We are basically looking for J-I that equals to k. (J-I == k) +In other words, given a J we are actually looking for an I that eauals to J-k. +If there are, 3 I that equals to J-k exists beforehand, there are 3 J-I combinations that equal to k. +ans += 3 +""" +class Solution(object): + def subarraySum(self, nums, k): + ans = 0 + + prefixSumCount = collections.Counter() + prefixSumCount[0] = 1 + + J = 0 + for n in nums: + J += n + I = J-k #the I that we are looking for. + ans += prefixSumCount[I] #there are "prefixSumCount[I]" combinations of J-I that equals to k. + prefixSumCount[J] += 1 + + return ans diff --git a/problems/python/subarrays-with-k-different-integers.py b/problems/python/subarrays-with-k-different-integers.py new file mode 100755 index 0000000..5753427 --- /dev/null +++ b/problems/python/subarrays-with-k-different-integers.py @@ -0,0 +1,25 @@ +class Solution(object): + def subarraysWithKDistinct(self, nums, K): + #number of subarray of nums which have at most k different numbers. + def atMost(k): + i = 0 + ans = 0 + counter = collections.Counter() + uniqueCount = 0 + + for j in xrange(len(nums)): + counter[nums[j]] += 1 + if counter[nums[j]]==1: uniqueCount += 1 + + while uniqueCount>k: + counter[nums[i]] -= 1 + if counter[nums[i]]==0: uniqueCount -= 1 + i += 1 + + # the logest subarray that ends at j is nums[i:j+1] + # nums[i:j+1] can produce j-i+1 subarrays that at most has k different number. + ans += j-i+1 + + return ans + + return atMost(K)-atMost(K-1) \ No newline at end of file diff --git a/problems/subdomain-visit-count.py b/problems/python/subdomain-visit-count.py old mode 100644 new mode 100755 similarity index 100% rename from problems/subdomain-visit-count.py rename to problems/python/subdomain-visit-count.py diff --git a/problems/subsets-ii.py b/problems/python/subsets-ii.py old mode 100644 new mode 100755 similarity index 100% rename from problems/subsets-ii.py rename to problems/python/subsets-ii.py diff --git a/problems/subsets.py b/problems/python/subsets.py old mode 100644 new mode 100755 similarity index 100% rename from problems/subsets.py rename to problems/python/subsets.py diff --git a/problems/python/substring-with-concatenation-of-all-words.py b/problems/python/substring-with-concatenation-of-all-words.py new file mode 100755 index 0000000..e3e6420 --- /dev/null +++ b/problems/python/substring-with-concatenation-of-all-words.py @@ -0,0 +1,52 @@ +""" +Time: O(N*wl), N is the length of the input string s; wl is the length of each word. +Space: O(N) + +For word count wc and word length wl, the string we are looking for will be between i and j (s[i:j]). +`test()` all s[i:j]. If pass, append into ans. + +Each `test()` will test the sub-string s[i:j]`. +If any "word" in the sub-string is not in the countExpected, the test failed. +If the word is used too many times (more than "countExpected"), the test failed. +Otherwise, the test pass. + +This is the solution I learn from @gabbu. +""" +import collections + +class Solution(object): + def findSubstring(self, s, words): + if not words: return [] + + wc = len(words) #word count + wl = len(words[0]) #word length + ans = [] + + i = 0 + j = wc*wl + + countExpected = collections.Counter(words) + + while j<=len(s): + if self.test(s[i:j], wl, countExpected): ans.append(i) + i += 1 + j += 1 + + return ans + + + def test(self, s, wl, countExpected): + counter = collections.Counter() #{word:how many time the word is used} + i = 0 + + while i=countExpected[word]: return False + i += wl + counter[word] += 1 + + return True + + + + diff --git a/problems/subtree-of-another-tree.py b/problems/python/subtree-of-another-tree.py old mode 100644 new mode 100755 similarity index 100% rename from problems/subtree-of-another-tree.py rename to problems/python/subtree-of-another-tree.py diff --git a/problems/python/sum-of-subarray-minimums.py b/problems/python/sum-of-subarray-minimums.py new file mode 100755 index 0000000..87eabf4 --- /dev/null +++ b/problems/python/sum-of-subarray-minimums.py @@ -0,0 +1,51 @@ +""" +[0] +Instead of listing iterate through all the subarrays (Which is O(N^2) in Time). +We think reversely. +For each num, how many subarrays such that the num is the minimum. +For each num, the count is (i-l) * (r-i) +Where l is the index of the first left number smaller to num. +Where r is the index of the first right number smaller to num. + +[1] +To construct nextSmaller, we iterate through the number, if it is incremental, we put it in the stack. +Once we encounter a number that is "not" incremental, then it is the next smaller number of stack[-1]. + +[2] +Since there are some the same number in the arr. If we use nextSmaller and prevSmaller to calculate, there will be some subarrays repeatly counted. +So we need to set up boundaries. So that even with the same number, they will not count the same subarray. + +Time: O(N) +Space: O(N) +""" +class Solution(object): + def sumSubarrayMins(self, arr): + N = len(arr) + ans = 0 + nextSmaller = [N]*N + prevSmallerOrEqual = [-1]*N #[2] + + #construct nextSmaller [1] + stack = [] + for i in xrange(N): + n = arr[i] + while stack and ns: + opt.append(str(nums[s])+'->'+str(nums[i-1])) + else: + opt.append(str(nums[s])) + s = i + return opt \ No newline at end of file diff --git a/problems/python/super-ugly-number.py b/problems/python/super-ugly-number.py new file mode 100755 index 0000000..7f91a65 --- /dev/null +++ b/problems/python/super-ugly-number.py @@ -0,0 +1,20 @@ +class Solution(object): + def nthSuperUglyNumber(self, n, primes): + p = [0]*len(primes) #p[i] stores the index of ugly number in ans that not yet times primes[i] yet + ans = [1] + h = [] + + for i in xrange(len(primes)): + heapq.heappush(h, (primes[i]*ans[p[i]], i)) + + for _ in xrange(n-1): + curr = h[0][0] + ans.append(curr) + + while h and h[0][0]==curr: + i = h[0][1] + heapq.heappop(h) + p[i] += 1 + heapq.heappush(h, (primes[i]*ans[p[i]], i)) + + return ans[-1] \ No newline at end of file diff --git a/problems/python/swap-adjacent-in-lr-string.py b/problems/python/swap-adjacent-in-lr-string.py new file mode 100755 index 0000000..a8acf65 --- /dev/null +++ b/problems/python/swap-adjacent-in-lr-string.py @@ -0,0 +1,18 @@ +class Solution(object): + def canTransform(self, start, end): + if len(start)!=len(end): return False + if start.replace('X', '')!=end.replace('X', ''): return False + + startLIndex = [i for i, c in enumerate(start) if c=='L'] + endLIndex = [i for i, c in enumerate(end) if c=='L'] + for i in xrange(len(startLIndex)): + if startLIndex[i]endRIndex[i]: + return False + + return True \ No newline at end of file diff --git a/problems/swap-nodes-in-pairs.py b/problems/python/swap-nodes-in-pairs.py old mode 100644 new mode 100755 similarity index 100% rename from problems/swap-nodes-in-pairs.py rename to problems/python/swap-nodes-in-pairs.py diff --git a/problems/swim-in-rising-water.py b/problems/python/swim-in-rising-water.py old mode 100644 new mode 100755 similarity index 100% rename from problems/swim-in-rising-water.py rename to problems/python/swim-in-rising-water.py diff --git a/problems/symmetric-tree.py b/problems/python/symmetric-tree.py old mode 100644 new mode 100755 similarity index 58% rename from problems/symmetric-tree.py rename to problems/python/symmetric-tree.py index 86330bb..bb5c717 --- a/problems/symmetric-tree.py +++ b/problems/python/symmetric-tree.py @@ -33,4 +33,35 @@ def isSymmetric(self, root): # if left_node.val!=right_node.val: return False # stack.append((left_node.right, right_node.left)) # stack.append((left_node.left, right_node.right)) -# return True \ No newline at end of file +# return True + +""" +Time: O(N) +Space: O(N) + +Use 2 DFS to traverse the tree at the same time. +s1 will go left first. +s2 will fo right first. +Make sure every element popping out is the same. +""" +class Solution(object): + def isSymmetric(self, root): + s1 = [root] + s2 = [root] + + while s1 and s2: + node1 = s1.pop() + node2 = s2.pop() + + if not node1 and not node2: continue + if not node1 and node2: return False + if node1 and not node2: return False + if node1.val!=node2.val: return False + + s1.append(node1.left) + s1.append(node1.right) + s2.append(node2.right) + s2.append(node2.left) + + if s1 or s2: return False #if there are something left in one of the stack + return True \ No newline at end of file diff --git a/problems/python/tallest-billboard.py b/problems/python/tallest-billboard.py new file mode 100755 index 0000000..587d1f6 --- /dev/null +++ b/problems/python/tallest-billboard.py @@ -0,0 +1,15 @@ +#TLE +class Solution(object): + def tallestBillboard(self, rods): + D = sum(rods) + N = len(rods) + + dp = [[float('-inf') for _ in xrange(-D, D+1)] for _ in xrange(N+1)] + dp[0][D] = 0 + + for i in xrange(1, N+1): + for d in xrange(-D, D+1): + h = rods[i-1] + dp[i][d+D] = max(dp[i-1][d+D], (dp[i-1][d+D-h]+h) if d+D-h>=0 else float('-inf'), dp[i-1][d+D+h] if d+D+h<2*D+1 else float('-inf')) + + return dp[N][D] \ No newline at end of file diff --git a/problems/python/target-sum.py b/problems/python/target-sum.py new file mode 100755 index 0000000..c216522 --- /dev/null +++ b/problems/python/target-sum.py @@ -0,0 +1,77 @@ +class Solution(object): + def findTargetSumWays(self, nums, S): + stack = [(0, 0)] + ans = 0 + + while stack: + i, s = stack.pop() + if i==len(nums) and s==S: ans += 1 + if i>=len(nums): continue + + stack.append((i+1, s+nums[i])) + stack.append((i+1, s-nums[i])) + + return ans + +""" +dp[i][j] = number of ways to sum to j using nums[0~i-1] +Time: O(SN) +Space: O(SN) +""" + + +import collections +class Solution(object): + def findTargetSumWays(self, nums, target): + S = sum(nums) + dp = [collections.Counter() for _ in xrange(len(nums)+1)] + dp[0][0] = 1 + + for i in xrange(1, len(nums)+1): + for j in xrange(-S, S+1): + dp[i][j] = dp[i-1][j+nums[i-1]] + dp[i-1][j-nums[i-1]] + + return dp[len(nums)][target] + + +#2021/6/7 +""" +dp[i][t] := number of ways nums[:i] can sum up to t applying + or -. +For all i, calculate all posible target (From minTarget~maxTarget) +""" +class Solution(object): + def findTargetSumWays(self, nums, target): + N = len(nums) + maxTarget = sum(nums) + minTarget = -maxTarget + + if targetmaxTarget: return 0 + + dp = [[0 for _ in xrange(minTarget, maxTarget+1)] for _ in xrange(N+1)] + + dp[0][0] = 1 + + for i in xrange(1, N+1): + for t in xrange(minTarget, maxTarget+1): + dp[i][t] = (dp[i-1][t-nums[i-1]] if t-nums[i-1]>=minTarget else 0) + (dp[i-1][t+nums[i-1]] if t+nums[i-1]<=maxTarget else 0) + + return dp[N][target] + + +""" +dp[i][s] := considerting nums[:i] how many expressions can sum up to s. +dp[0][0] = 1 +""" +class Solution(object): + def findTargetSumWays(self, nums, target): + S = sum(nums) + if not -S<=target<=S: return 0 + + dp = [{s:0 for s in xrange(-S, S+1)} for _ in xrange(len(nums)+1)] + dp[0][0] = 1 + + for i in xrange(1, len(nums)+1): + for s in xrange(-S, S+1): + dp[i][s] = (dp[i-1][s+nums[i-1]] if s+nums[i-1]<=S else 0) + (dp[i-1][s-nums[i-1]] if s-nums[i-1]>=-S else 0) + + return dp[-1][target] \ No newline at end of file diff --git a/problems/task-scheduler.py b/problems/python/task-scheduler.py old mode 100644 new mode 100755 similarity index 100% rename from problems/task-scheduler.py rename to problems/python/task-scheduler.py diff --git a/problems/python/text-justification.py b/problems/python/text-justification.py new file mode 100755 index 0000000..98eb0be --- /dev/null +++ b/problems/python/text-justification.py @@ -0,0 +1,68 @@ +""" +The description of this problem is unclear. +"Extra spaces between words should be distributed as evenly as possible." Should be "Extra spaces between words IN EACH LINE should be distributed as evenly as possible." +So the problem in short is: +1. Every line should have as many as word as possible. But they should be separate by space or spaces. +2. Mid align all the line. Except the last line and the lines with one word. +3. For mid align, distribute space evenly, if there are extra spaces, it should be located left. For example 5 spaces distribute to 2 places, it should be 3 2, not 2 3. 7 spaces distribute to 3 places, it should be 3 2 2, not 2 3 2, not 2 2 3. + +Time: O(N), N is the number of words. +Space: O(W), W is maxWidth for keeping strings in currLine. Can further reduce to O(1) using only index. +""" +class Solution(object): + def fullJustify(self, words, maxWidth): + def canAddToCurrLine(word, currLineStringLength): + currWidth = len(currLine)-1 + currLineStringLength # space+currLineStringLength + return currWidth+len(word)+1<=maxWidth + + def leftAlign(currLine): + line = '' + + for word in currLine: + line += word + ' ' + line = line[:-1] #remove last space + + line += ' '*(maxWidth-len(line)) + return line + + def midAlign(currLine, currLineStringLength): + line = '' + + totalSpaceCount = maxWidth-currLineStringLength + extraSpaceCount = totalSpaceCount%(len(currLine)-1) + spaceCount = totalSpaceCount-extraSpaceCount + spaces = ' '*(totalSpaceCount/(len(currLine)-1)) + + for word in currLine: + line += word + if spaceCount>0: + line += spaces + spaceCount -= len(spaces) + + if extraSpaceCount>0: + line += ' ' + extraSpaceCount -= 1 + + return line + + currLineStringLength = 0 + currLine = [] + ans = [] + + i = 0 + while i=N or j<0 or j>=M: continue + if maze[i][j]==1: continue + + if i==destination[0] and j==destination[1] and direction=='stop': return dis + + if direction=='stop': + heapq.heappush(pq, (dis+1, i-1, j, 'left')) + heapq.heappush(pq, (dis+1, i+1, j, 'right')) + heapq.heappush(pq, (dis+1, i, j-1, 'down')) + heapq.heappush(pq, (dis+1, i, j+1, 'up')) + elif direction=='left': + if i-1<0 or i-1>=N or j<0 or j>=M or maze[i-1][j]==1: + heapq.heappush(pq, (dis, i, j, 'stop')) + else: + heapq.heappush(pq, (dis+1, i-1, j, direction)) + elif direction=='right': + if i+1<0 or i+1>=N or j<0 or j>=M or maze[i+1][j]==1: + heapq.heappush(pq, (dis, i, j, 'stop')) + else: + heapq.heappush(pq, (dis+1, i+1, j, direction)) + elif direction=='down': + if i<0 or i>=N or j-1<0 or j-1>=M or maze[i][j-1]==1: + heapq.heappush(pq, (dis, i, j, 'stop')) + else: + heapq.heappush(pq, (dis+1, i, j-1, direction)) + elif direction=='up': + if i<0 or i>=N or j+1<0 or j+1>=M or maze[i][j+1]==1: + heapq.heappush(pq, (dis, i, j, 'stop')) + else: + heapq.heappush(pq, (dis+1, i, j+1, direction)) + + return -1 \ No newline at end of file diff --git a/problems/time-based-key-value-store.py b/problems/python/time-based-key-value-store.py old mode 100644 new mode 100755 similarity index 100% rename from problems/time-based-key-value-store.py rename to problems/python/time-based-key-value-store.py diff --git a/problems/to-lower-case.py b/problems/python/to-lower-case.py old mode 100644 new mode 100755 similarity index 100% rename from problems/to-lower-case.py rename to problems/python/to-lower-case.py diff --git a/problems/python/toeplitz-matrix.py b/problems/python/toeplitz-matrix.py new file mode 100755 index 0000000..431ce27 --- /dev/null +++ b/problems/python/toeplitz-matrix.py @@ -0,0 +1,22 @@ +class Solution(object): + def isToeplitzMatrix(self, matrix): + def check(i0, j0, matrix): + i = i0 + j = j0 + + while 0<=i=k: break + + return opt[:k] + +# heap +class Solution(object): + def topKFrequent(self, nums, k): + opt = [] + counter = collections.Counter(nums) + + heap = [(-count, num) for num, count in counter.items()] + heapq.heapify(heap) + + while len(opt)0: + _, num = heapq.heappop(h) + ans.append(num) + k -= 1 + + return ans + +""" +Bucket sort for counts. +Space/Time: O(N) +""" +class Solution(object): + def topKFrequent(self, nums, k): + counter = collections.Counter(nums) + count2Nums = [[] for _ in xrange(len(nums)+1)] #count2Nums[i] := elements with count i + + for num in counter: + count2Nums[counter[num]].append(num) + + ans = [] + for i in xrange(len(count2Nums)-1, -1, -1): + ans += count2Nums[i] + if len(ans)>=k: break + + return ans + + +# Quick Select +class Solution(object): + def topKFrequent(self, nums, K): + def quickselect(A, s, e, K): + i = s + t = s + j = e + + pivot = A[(s+e)/2][0] + while t<=j: + if A[t][0]=K: + return quickselect(A, j+1, e, K) + elif e-(i-1)>=K: + return pivot + else: + return quickselect(A, s, i-1, K-(e-i+1)) + + ans = [] + counter = collections.Counter(nums) + freqs = [(counter[num], num) for num in counter] + + KthLargestFreq = quickselect(freqs, 0, len(freqs)-1, K) + + for freq, num in freqs: + if freq>=KthLargestFreq: ans.append(num) + + return ans \ No newline at end of file diff --git a/problems/python/trapping-rain-water-ii.py b/problems/python/trapping-rain-water-ii.py new file mode 100755 index 0000000..dd14bc7 --- /dev/null +++ b/problems/python/trapping-rain-water-ii.py @@ -0,0 +1,28 @@ +class Solution(object): + def trapRainWater(self, heightMap): + pq = [] + N = len(heightMap) + M = len(heightMap[0]) + visited = set() + ans = 0 + curr = float('-inf') + + for i in xrange(N): + for j in xrange(M): + if i==0 or i==N-1 or j==0 or j==M-1: + heapq.heappush(pq, (heightMap[i][j], i, j)) + + while pq: + h, i, j = heapq.heappop(pq) + if (i, j) in visited: continue + visited.add((i, j)) + + if h>curr: curr = h + ans += (curr-h) + + for iNext, jNext in [(i+1, j), (i-1, j), (i, j+1), (i, j-1)]: + if iNext<0 or iNext>=N or jNext<0 or jNext>=M: continue + if (iNext, jNext) in visited: continue + heapq.heappush(pq, (heightMap[iNext][jNext], iNext, jNext)) + + return ans \ No newline at end of file diff --git a/problems/python/trapping-rain-water.py b/problems/python/trapping-rain-water.py new file mode 100755 index 0000000..7791f4e --- /dev/null +++ b/problems/python/trapping-rain-water.py @@ -0,0 +1,26 @@ +""" +Let's calculate the answer considering only the left boandary. +The water each i can contain is wall[i-1]-height[i] +if no water at i-1 the wall[i-1] will be height[i-1], else it should be height[i-1]+water[i-1] +=> water[i] = (height[i-1] + water[i-1]) - height[i] (l2r below) + +Do the same from right to left, this time considering only the right boandary. + +The amount of water at i considering both left and right wall will be min(l2r[i], r2l[i]) +""" +class Solution(object): + def trap(self, height): + N = len(height) + l2r = [0]*N #water + r2l = [0]*N #water + + for i in xrange(1, N): + l2r[i] = max(l2r[i-1]+height[i-1]-height[i], 0) + + for i in xrange(N-2, -1, -1): + r2l[i] = max(r2l[i+1]+height[i+1]-height[i], 0) + + ans = 0 + for i in xrange(N): + ans += min(l2r[i], r2l[i]) + return ans \ No newline at end of file diff --git a/problems/trim-a-binary-search-tree.py b/problems/python/trim-a-binary-search-tree.py old mode 100644 new mode 100755 similarity index 100% rename from problems/trim-a-binary-search-tree.py rename to problems/python/trim-a-binary-search-tree.py diff --git a/problems/python/two-out-of-three.py b/problems/python/two-out-of-three.py new file mode 100755 index 0000000..8518903 --- /dev/null +++ b/problems/python/two-out-of-three.py @@ -0,0 +1,17 @@ +class Solution(object): + def twoOutOfThree(self, nums1, nums2, nums3): + ans = [] + + counter = collections.Counter() + + for num in list(set(nums1)): + counter[num] += 1 + for num in list(set(nums2)): + counter[num] += 1 + for num in list(set(nums3)): + counter[num] += 1 + + for num in counter: + if counter[num]>=2: ans.append(num) + + return ans \ No newline at end of file diff --git a/problems/two-sum-ii-input-array-is-sorted.py b/problems/python/two-sum-ii-input-array-is-sorted.py old mode 100644 new mode 100755 similarity index 100% rename from problems/two-sum-ii-input-array-is-sorted.py rename to problems/python/two-sum-ii-input-array-is-sorted.py diff --git a/problems/python/two-sum.py b/problems/python/two-sum.py new file mode 100755 index 0000000..3d8013e --- /dev/null +++ b/problems/python/two-sum.py @@ -0,0 +1,60 @@ +#https://leetcode.com/problems/two-sum/ +""" +when we iterate through the nums +we don't know if the counter part exist +so we store the counter part we want in the 'wanted' with the index now +so inside the wanted is {the_counter_part_we_want:index_now} [0] + +later on if we found the num is in 'wanted' +we return the index_now and the index_now we previously stored in the 'wanted' [1] +""" +class Solution(object): + def twoSum(self, nums, target): + wanted = {} + for i in xrange(len(nums)): + n = nums[i] + if n in wanted: #[1] + return [wanted[n], i] + else: + wanted[target-n] = i #[0] + return [] + + + +#2021/5/17 +class Solution(object): + def twoSum(self, nums, target): + M = {} #counter part number needed by index i: i + + for i, num in enumerate(nums): + if num in M: return (M[num], i) + M[target-num] = i + + return False + +#2021/7/4 +class Solution(object): + def twoSum(self, nums, target): + memo = {} #{key : value} := {"counter part needed for n" : "index of n"} + + for i, n in enumerate(nums): + if n in memo: + return [memo[n], i] + else: + memo[target-n] = i + return [] + +#2021/7/31 +""" +Time: O(N) +Space: O(N) +""" +class Solution(object): + def twoSum(self, nums, target): + memo = {} #{key : value} := {"counter part needed for n" : "index of n"} + + for i, n in enumerate(nums): + if n in memo: return [memo[n], i] + memo[target-n] = i + + return [] \ No newline at end of file diff --git a/problems/python/ugly-number-ii.py b/problems/python/ugly-number-ii.py new file mode 100755 index 0000000..38879f5 --- /dev/null +++ b/problems/python/ugly-number-ii.py @@ -0,0 +1,31 @@ +""" +`ans` contains sorted ugly numbers. +For each ugly number we can x2 or x3 or x5 to produce another ugly number. + +i2 points at the number that has been x2 yet. +i3 points at the number that has been x3 yet. +i5 points at the number that has been x5 yet. + +Each time we append one minimum ugly number and move the pointer. + +Time: O(N) +Space: O(N) +""" +class Solution(object): + def nthUglyNumber(self, k): + i2 = i3 = i5 = 0 + + ans = [1] + while len(ans)=0 and uf.isLand((x-1)*n+y)): connected.append((x-1)*n+y) + if (x+1=0 and uf.isLand(x*n+y-1)): connected.append(x*n+y-1) + if (y+1=0 + + def setLand(self, i): + self.parents[i] = i + self.count += 1 + + def find(self, i): + if self.parents[i]!=i: self.parents[i] = self.find(self.parents[i]) + return self.parents[i] + + def union(self, i1, i2): + parent1 = self.find(i1) + parent2 = self.find(i2) + + if parent1!=parent2: + if self.ranks[parent1]>self.ranks[parent2]: + self.parents[parent2] = parent1 + elif self.ranks[parent1]len(s): t, s = s, t + + for c in s: counter[c] += 1 + for c in t: counter[c] -= 1 + for c in counter: + if counter[c]>0: return False + return True + \ No newline at end of file diff --git a/problems/python/valid-number.py b/problems/python/valid-number.py new file mode 100755 index 0000000..9d0793a --- /dev/null +++ b/problems/python/valid-number.py @@ -0,0 +1,44 @@ +class Solution(object): + def isNumber(self, s): + def isOK(s, maxDots): + if not s: return False + + #check and remove +/- + for i, c in enumerate(s): + if (c=='+' or c=='-') and i!=0: + return False + if s[0]=='+' or s[0]=='-': s = s[1:] + + #check dot and if there is digit + dotCount = 0 + dotPos = 0 + digitCount = 0 + for i, c in enumerate(s): + if c=='.': + dotCount += 1 + dotPos = i + elif c.isdigit(): + digitCount += 1 + + if dotCount>maxDots: return False + if digitCount==0: return False + + return True + + #get e's position. Also check if all char is in validChar + validChar = set(['1','2', '3', '4', '5', '6', '7', '8', '9', '0', 'e', 'E', '.', '+', '-']) + eCount = 0 + ePos = 0 + for i, c in enumerate(s): + if c not in validChar: + return False + if c=='e' or c=='E': + eCount += 1 + ePos = i + + if eCount>1: + return False + elif eCount==1: + return isOK(s[:ePos], 1) and isOK(s[ePos+1:], 0) + else: + return isOK(s, 1) diff --git a/problems/python/valid-palindrome-ii.py b/problems/python/valid-palindrome-ii.py new file mode 100755 index 0000000..84bce52 --- /dev/null +++ b/problems/python/valid-palindrome-ii.py @@ -0,0 +1,29 @@ +""" +When seeing the first different chars in the string. +Check if anyone of the string is palindrome if we remove one of them. + +Time: O(N) +Space: O(1) +""" +class Solution(object): + def validPalindrome(self, s): + def isPalindrome(s): + i = 0 + j = len(s)-1 + while i=max_val: - return False - - left_valid = helper(node.left, min_val, node.val) - if not left_valid: return False - right_valid = helper(node.right, node.val, max_val) - if not right_valid: return False - + if not node: return True + if node.val<=min_val or node.val>=max_val:return False + if not helper(node.left, min_val, node.val): return False + if not helper(node.right, node.val, max_val): return False return True - return helper(root, float('-inf'), float('inf')) + return helper(node, float('-inf'), float('inf')) #iterative - def isValidBST(self, root): - if root==None: return True - stack = [(root, float('-inf'), float('inf'))] + def isValidBST(self, node): + if node==None: return True + stack = [(node, float('-inf'), float('inf'))] while stack: node, min_val, max_val = stack.pop() if node.val<=min_val or node.val>=max_val: @@ -57,18 +51,47 @@ def isValidBST(self, root): return True #in-order traversal - def isValidBST(self, root): + def isValidBST(self, node): stack = [] last_val = float('-inf') - while root or stack: - while root: - stack.append(root) - root = root.left - root = stack.pop() + while node or stack: + while node: + stack.append(node) + node = node.left + node = stack.pop() - if root.val<=last_val: + if node.val<=last_val: return False - last_val = root.val - root = root.right #if root.right: root = root.right + last_val = node.val + node = node.right #if node.right: node = node.right return True + + + +#2021/9/14 +""" +Time: O(N) +Space: O(N) + +Use in-order traversal to check. If the bst is valid, the node.val should always be larger than the prev. +""" +class Solution(object): + def isValidBST(self, root): + node = root + stack = [] + prev = float('-inf') + + while node or stack: + while node: + stack.append(node) + node = node.left + + node = stack.pop() + + if prev>=node.val: return False + prev = node.val + + node = node.right + + return True \ No newline at end of file diff --git a/problems/python/verify-preorder-serialization-of-a-binary-tree.py b/problems/python/verify-preorder-serialization-of-a-binary-tree.py new file mode 100755 index 0000000..39930bf --- /dev/null +++ b/problems/python/verify-preorder-serialization-of-a-binary-tree.py @@ -0,0 +1,14 @@ +""" +Time: O(N) +Space: O(N) for creating the list preorder.split(',') +""" +class Solution(object): + def isValidSerialization(self, preorder): + slot = 1 + + for c in preorder.split(','): + slot -= 1 #each elemet consumes a slot + if slot<0: return False + if c!='#': slot += 2 #each non-null node also create 2 slot + + return slot==0 #all slots should be fill \ No newline at end of file diff --git a/problems/python/vertical-order-traversal-of-a-binary-tree.py b/problems/python/vertical-order-traversal-of-a-binary-tree.py new file mode 100755 index 0000000..bda15ad --- /dev/null +++ b/problems/python/vertical-order-traversal-of-a-binary-tree.py @@ -0,0 +1,67 @@ +""" +The description wants us to report from left to right, top to bottom. +Thus, we keep add value into `temp` with `x_position` as the key and `(y_position, node.val)` as the value. +And maintain `min_x`, `max_x` at the same time. So we know how to iterate the `temp`. +""" +from collections import defaultdict + +class Solution(object): + def verticalTraversal(self, root): + temp = defaultdict(list) + min_x = 0 + max_x = 0 + + stack = [] + stack.append((root, 0, 0)) + while stack: + node, x, y = stack.pop() + temp[x].append((y, node.val)) #append the value of height, so we can sort by height later on + min_x = min(min_x, x) + max_x = max(max_x, x) + if node.left: stack.append((node.left, x-1, y+1)) + if node.right: stack.append((node.right, x+1, y+1)) + + opt = [] + for i in range(min_x, max_x+1): + opt.append([v for y, v in sorted(temp[i])]) #the temp[i] will be sorted by y_position then sorted by node.val + + return opt + + +""" +Time: O(Nk) in average case. +dfs takes O(N). +Double for-loop take nearly O(N), in the double for-loop we need to spend some time on sorting. +But the number of elements in the same column and row is very very small. Lets say, O(k) + +Space: O(N) +""" +class Solution(object): + def verticalTraversal(self, root): + def dfs(node, x, y): + if not node: return + self.maxX = max(self.maxX, x) + self.minX = min(self.minX, x) + self.minY = min(self.minY, y) + data[(x, y)].append(node.val) + dfs(node.left, x-1, y-1) + dfs(node.right, x+1, y-1) + + data = collections.defaultdict(list) + self.maxX = float('-inf') + self.minX = float('inf') + self.minY = float('inf') + + dfs(root, 0, 0) + + ans = [] + for x in xrange(self.minX, self.maxX+1): + temp = [] + for y in xrange(0, self.minY-1, -1): + if (x, y) not in data: continue + if len(data[(x, y)])>1: + temp += sorted(data[(x, y)]) + else: + temp.append(data[(x, y)][0]) + ans.append(temp) + return ans \ No newline at end of file diff --git a/problems/python/wiggle-subsequence.py b/problems/python/wiggle-subsequence.py new file mode 100755 index 0000000..e44e632 --- /dev/null +++ b/problems/python/wiggle-subsequence.py @@ -0,0 +1,64 @@ +""" +dp[0][0 or 1] is considering no num in nums. +dp[1][0 or 1] is considering the first num in nums. +dp[2][0 or 1] is considering the second num in nums. +dp[3][0 or 1] is considering the third num in nums. +... + +dp[i][0] := longest length of wiggle seq that ends at nums[i-1] and diff ends in positive number. +dp[i][1] := longest length of wiggle seq that ends at nums[i-1] and diff ends in negative number. + +Time: O(N) +Space: O(N) +""" +class Solution(object): + def wiggleMaxLength(self, nums): + ans = 1 + N = len(nums) + + dp = [[0, 0] for _ in xrange(N+1)] + dp[1][0] = 1 + dp[1][1] = 1 + + for i in xrange(2, N+1): + if nums[i-1]-nums[i-2]>0: + dp[i][0] = dp[i-1][1]+1 + dp[i][1] = dp[i-1][1] + elif nums[i-1]-nums[i-2]<0: + dp[i][1] = dp[i-1][0]+1 + dp[i][0] = dp[i-1][0] + else: + dp[i][1] = dp[i-1][1] + dp[i][0] = dp[i-1][0] + + ans = max(ans, dp[i][0], dp[i][1]) + + return ans + +""" +From the above solution, we notice that we do not need whole array to keep track. +Just need two variables. +Use `up` and `down` to replace dp[i-1][0] and dp[i-1][1] + +Time: O(N) +Space: O(1) +""" +class Solution(object): + def wiggleMaxLength(self, nums): + N = len(nums) + + ans = 1 + up = 1 + down = 1 + + for i in xrange(2, N+1): + if nums[i-1]-nums[i-2]>0: + up, down = down+1, down + elif nums[i-1]-nums[i-2]<0: + down = up+1 + + ans = max(ans, up, down) + + return ans + + diff --git a/problems/python/word-break.py b/problems/python/word-break.py new file mode 100755 index 0000000..b2fd2de --- /dev/null +++ b/problems/python/word-break.py @@ -0,0 +1,17 @@ +class Solution(object): + def wordBreak(self, s, wordDict): + def helper(s_left): + if not s_left: return True + if s_left in history: return history[s_left] + + for word in wordDict: + if len(s_left)shortest: break + + if word==endWord: + shortest = len(path) + ans.append(path.keys()) + continue + + for i in xrange(len(word)): + s = word[:i]+'#'+word[i+1:] + for wordNext in a[s]: + q.append((wordNext, path.copy())) + + return ans + +""" +Time: O(VL+E). +Building V ajacencyList takes O(V*L*26) => O(VL). DFS takes O(V+E) +V is the number of words. L is the length of each word. E is the number of edge of each vertext (word). +Space: O(V+E) + +Build a special ranked adjacency list. +Different from the normal adjacency list, the list will only contain the ones the is farther away from the beginWord. +In other words, the adjacency nodes you get will not will not cercle back to the direction of the beginWord. +This assures us the result will be "shortest" path. + +Next, only standard dfs is needed. + +If you did not build the special adjacency list, you might need to check if a new node is already in the path or not. +This will cause TLE. +Or cause MLE if you use a hashset for the visited node in the path... + +This solution is inspired by https://www.youtube.com/watch?v=mIZJIuMpI2M. +""" +class Solution(object): + def findLadders(self, beginWord, endWord, wordList): + ans = [] + wordSet = set(wordList) + ajacencyList = self.buildAjacencyList(beginWord, wordSet) + stack = [(beginWord, [beginWord])] + + while stack: + word, path = stack.pop() + if word==endWord: ans.append(path) + + for nextWord in ajacencyList[word]: + stack.append((nextWord, path+[nextWord])) + + return ans + + def buildAjacencyList(self, beginWord, wordSet): + ajacencyList = collections.defaultdict(list) + + rank = {} + rank[beginWord] = 0 + + q = collections.deque() + q.append(beginWord) + + while q: + word = q.popleft() + for i in xrange(len(word)): + for alphabet in 'abcdefghijklmnopqrstuvwxyz': + nextWord = word[:i]+alphabet+word[i+1:] + + if nextWord not in wordSet or word==nextWord: continue + + if nextWord not in rank: + rank[nextWord] = rank[word]+1 + ajacencyList[word].append(nextWord) + q.append(nextWord) + elif rank[word]=0: opt.append((i-1, j)) + if j-1>=0: opt.append((i, j-1)) + return opt + + def dfs(i, j, l, word): + if word in found: return + if board[i][j]!=word[l]: return + + if l==len(word)-1 and board[i][j]==word[l]: + opt.append(word) + found.add(word) + return + + char = board[i][j] + board[i][j] = '#' + + for ni, nj in getNeighbor(i, j): + dfs(ni, nj, l+1, word) + + board[i][j] = char + + opt = [] + M = len(board) + N = len(board[0]) + found = set() + + for i in xrange(M): + for j in xrange(N): + for word in words: + if word not in found: + dfs(i, j, 0, word) + return opt + + +board = [ + ['o','a','a','n'], + ['e','t','a','e'], + ['i','h','k','r'], + ['i','f','l','v'] +] +words = ["oath","pea","eat","rain"] + +print Solution().findWords(board, words) + + + + +class Solution(object): + def findWords(self, board, words): + def dfs(i, j, parent): + c = board[i][j] + node = parent[c] + + if '.' in node: ans.append(node.pop('.')) + + board[i][j] = '#' + + for (rowOffset, colOffset) in [(-1, 0), (0, 1), (1, 0), (0, -1)]: + newRow, newCol = i + rowOffset, j + colOffset + if newRow<0 or newRow>=len(board) or newCol<0 or newCol>=len(board[0]): continue + if not board[newRow][newCol] in node: continue + dfs(newRow, newCol, node) + + board[i][j] = c + if not node: parent.pop(c) + + trie = {} + ans = [] + + #build trie + for word in words: + node = trie + for c in word: + if c not in node: + node[c] = {} + node = node[c] + node['.'] = word + print trie + + for i in xrange(len(board)): + for j in xrange(len(board[0])): + if board[i][j] in trie: dfs(i, j, trie) + + + return ans + + + + \ No newline at end of file diff --git a/problems/word-search.py b/problems/python/word-search.py old mode 100644 new mode 100755 similarity index 100% rename from problems/word-search.py rename to problems/python/word-search.py diff --git a/problems/python3/3sum-closest.py b/problems/python3/3sum-closest.py new file mode 100755 index 0000000..472c80f --- /dev/null +++ b/problems/python3/3sum-closest.py @@ -0,0 +1,24 @@ +""" +Time: O(N^2) +Space: O(1) +""" +class Solution: + def threeSumClosest(self, nums: List[int], target: int) -> int: + nums.sort() + + N = len(nums) + ans = float('inf') + + for i in range(N): + j = i+1 + k = N-1 + + while jabs(target-total): ans = total + if total>target: + k -= 1 + elif total int: + N = len(nums) + ans = 0 + + nums.sort() + + + for i in range(N): + j = i+1 + k = N-1 + while j List[List[int]]: + ans = [] + nums.sort() + + for i in range(len(nums)): + if 00: break #[1] + + j, k = i+1, len(nums)-1 + while j0: + k -= 1 + elif nums[i]+nums[j]+nums[k]<0: + j += 1 + else: + ans.append((nums[i], nums[j], nums[k])) + while 0 List[List[int]]: + ans = set() + seen = set() + N = len(nums) + + for i, v1 in enumerate(nums): + if v1 in seen: continue + seen.add(v1) + needed = set() + for j, v2 in enumerate(nums[i+1:]): + if v2 in needed: + ans.add(tuple(sorted((v1, v2, -v1-v2)))) + needed.add(-v1-v2) + return ans + + + +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + ans = [] + + nums.sort() + + for i in range(len(nums)): + if nums[i]>0: break + if i>0 and nums[i]==nums[i-1]: continue + + j = i+1 + k = len(nums)-1 + while j0: + k -= 1 + elif nums[j]+nums[k]+nums[i]<0: + j += 1 + else: + ans.append((nums[i], nums[j], nums[k])) + + while j O(N^3) +Space: O(K) -> O(1) +""" +class Solution: + def fourSum(self, nums: List[int], target: int) -> List[List[int]]: + def kSum(k, start, target): + if k>2: + for i in range(start, len(nums)-k+1): + if i!=start and nums[i]==nums[i-1]: continue + temp.append(nums[i]) + kSum(k-1, i+1, target-nums[i]) + temp.pop() + else: + l, r = start, len(nums)-1 + + while ltarget: + r -= 1 + elif nums[l]+nums[r] Optional[ListNode]: + carry = 0 + dummy = ListNode() + curr = dummy + + while l1 or l2 or carry: + n = (l1.val if l1 else 0) + (l2.val if l2 else 0) + carry + if n>=10: + curr.next = ListNode(n-10) + carry = 1 + else: + curr.next = ListNode(n) + carry = 0 + curr = curr.next + if l1: l1 = l1.next + if l2: l2 = l2.next + + return dummy.next \ No newline at end of file diff --git a/problems/python3/alien-dictionary.py b/problems/python3/alien-dictionary.py new file mode 100644 index 0000000..23ca1ee --- /dev/null +++ b/problems/python3/alien-dictionary.py @@ -0,0 +1,36 @@ +class Solution: + def alienOrder(self, words: List[str]) -> str: + adj = collections.defaultdict(list) + inbounds = collections.Counter() + q = collections.deque() + ans = '' + + adj = {c: set() for word in words for c in word} + for i in range(len(words)-1): + w1, w2 = words[i], words[i+1] + minLen = min(len(w1), len(w2)) + if w1[:minLen]==w2[:minLen] and len(w1)>len(w2): return "" + + for j in range(minLen): + if w1[j]!=w2[j]: + adj[w1[j]].add(w2[j]) + break + + for c in adj: + for nc in list(adj[c]): + inbounds[nc] += 1 + + for c in adj: + if inbounds[c]==0: q.append(c) + + while q: + c = q.popleft() + + ans += c + + for nc in adj[c]: + inbounds[nc] -= 1 + if inbounds[nc]==0: q.append(nc) + + return ans if len(ans)==len(adj) else '' + diff --git a/problems/python3/balanced-binary-tree.py b/problems/python3/balanced-binary-tree.py new file mode 100755 index 0000000..65c749e --- /dev/null +++ b/problems/python3/balanced-binary-tree.py @@ -0,0 +1,18 @@ +""" +Time: O(N) +Space: O(LogN) Recursion stack. If the tree is balanced. +""" +class Solution: + def isBalanced(self, root: Optional[TreeNode]) -> bool: + def getHeight(node) -> (bool, int): + if not node: return True, 0 + leftIsBalanced, leftHeight = getHeight(node.left) + if not leftIsBalanced: return False, 0 + + rightIsBalanced, rightHeight = getHeight(node.right) + if not rightIsBalanced: return False, 0 + + return abs(leftHeight-rightHeight)<2, 1+max(leftHeight, rightHeight) + + isBalanced, h = getHeight(root) + return isBalanced \ No newline at end of file diff --git a/problems/python3/best-time-to-buy-and-sell-stock-with-cooldown.py b/problems/python3/best-time-to-buy-and-sell-stock-with-cooldown.py new file mode 100644 index 0000000..06644e3 --- /dev/null +++ b/problems/python3/best-time-to-buy-and-sell-stock-with-cooldown.py @@ -0,0 +1,16 @@ +#dp[i][0] := max profit when last action is buy +#dp[i][1] := max profit when last action is sell +class Solution: + def maxProfit(self, prices: List[int]) -> int: + if not prices or len(prices)<=1: return 0 + + N = len(prices) + dp = [[0, 0] for _ in range(N)] + dp[0] = [-prices[0], 0] + dp[1][0] = max(-prices[1], -prices[0]) + dp[1][1] = max(prices[1]+dp[0][0], dp[0][1]) + + for i in range(2, N): + dp[i][0] = max(dp[i-2][1]-prices[i], dp[i-1][0]) + dp[i][1] = max(prices[i]+dp[i-1][0], dp[i-1][1]) + return max(dp[-1][0], dp[-1][1], 0) \ No newline at end of file diff --git a/problems/python3/best-time-to-buy-and-sell-stock.py b/problems/python3/best-time-to-buy-and-sell-stock.py new file mode 100644 index 0000000..f04ad91 --- /dev/null +++ b/problems/python3/best-time-to-buy-and-sell-stock.py @@ -0,0 +1,10 @@ +class Solution: + def maxProfit(self, prices: List[int]) -> int: + minPrice = prices[0] + ans = 0 + + for i in range(1, len(prices)): + ans = max(ans, prices[i]-minPrice) + minPrice = min(prices[i], minPrice) + + return ans \ No newline at end of file diff --git a/problems/python3/binary-search.py b/problems/python3/binary-search.py new file mode 100644 index 0000000..2e65a63 --- /dev/null +++ b/problems/python3/binary-search.py @@ -0,0 +1,16 @@ +class Solution: + def search(self, nums: List[int], target: int) -> int: + l = 0 + r = len(nums)-1 + + while l<=r: + m = l + int((r-l)/2) + + if nums[m]>target: + r = m-1 + elif nums[m] List[List[int]]: + def helper(node, level): + if not node: return + if level==len(ans): ans.append([]) + ans[level].append(node.val) + helper(node.left, level+1) + helper(node.right, level+1) + + ans = [] + helper(root, 0) + + return ans + + +""" +Time: O(N), since we need to go through all the nodes. +Space: O(N) for queue. +""" +class Solution: + def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: + if not root: return [] + + q = collections.deque([(root, 0)]) + ans = [] + + while q: + node, level = q.popleft() + + if level==len(ans): ans.append([]) + ans[level].append(node.val) + if node.left: q.append((node.left, level+1)) + if node.right: q.append((node.right, level+1)) + + return ans \ No newline at end of file diff --git a/problems/python3/binary-tree-maximum-path-sum.py b/problems/python3/binary-tree-maximum-path-sum.py new file mode 100644 index 0000000..e024506 --- /dev/null +++ b/problems/python3/binary-tree-maximum-path-sum.py @@ -0,0 +1,27 @@ +""" +helper(node) will return the max path sum from the node to the leaf. [0] + +Along the way we update the ans. [1] +The path of ans must pass through one of the node in the tree. +`node.val+left+right` means the max path sum that pass through the node. +So after all `helper()` is executed, we tested all the nodes. + +Since there are some negative values in the tree, each node can decided not to take the negative path sum into account. [3] + +Time: O(N) +Space: O(LogN) for the recursive stack (if the tree is balanced). +""" +class Solution: + def maxPathSum(self, root: Optional[TreeNode]) -> int: + def helper(node): + nonlocal ans + + if not node: return 0 + left = max(helper(node.left), 0) #[3] + right = max(helper(node.right), 0) #[3] + ans = max(ans, node.val+left+right) #[1] + return node.val+max(left, right) #[0] + + ans = float('-inf') + helper(root) + return ans \ No newline at end of file diff --git a/problems/python3/binary-tree-right-side-view.py b/problems/python3/binary-tree-right-side-view.py new file mode 100755 index 0000000..53b4d13 --- /dev/null +++ b/problems/python3/binary-tree-right-side-view.py @@ -0,0 +1,18 @@ +""" +Time: O(N) +Space: O(LogN) if the tree is balanced. + +BFS is a more intuitive way to do this, but the time complexity will be at least O(N). +Using recursion instead, will take O(LogN) for the recursion stack size. +""" +class Solution: + def rightSideView(self, root: Optional[TreeNode]) -> List[int]: + def helper(node, level): + if not node: return + if len(ans) int: + def dfs(l, r)->int: + if l>r: return 0 + if (l, r) in dp: return dp[(l, r)] + + dp[(l, r)] = 0 + for i in range(l, r+1): + dp[(l, r)] = max(dp[(l, r)], nums[l-1]*nums[i]*nums[r+1] + dfs(l, i-1) + dfs(i+1, r)) + return dp[(l, r)] + + nums = [1]+nums+[1] + dp = {} + return dfs(1, len(nums)-2) \ No newline at end of file diff --git a/problems/python3/car-fleet.py b/problems/python3/car-fleet.py new file mode 100644 index 0000000..24ccba8 --- /dev/null +++ b/problems/python3/car-fleet.py @@ -0,0 +1,16 @@ +class Solution: + def carFleet(self, target: int, position: List[int], speed: List[int]) -> int: + N = len(position) + + timeInfo = [] + for i in range(N): + timeInfo.append((position[i], (target-position[i])/speed[i])) + timeInfo.sort(reverse=True) + + stack = [] + for _, time in timeInfo: + stack.append(time) + if len(stack)>=2 and stack[-2]>=stack[-1]: + stack.pop() + + return len(stack) \ No newline at end of file diff --git a/problems/python3/cheapest-flights-within-k-stops.py b/problems/python3/cheapest-flights-within-k-stops.py new file mode 100644 index 0000000..4595802 --- /dev/null +++ b/problems/python3/cheapest-flights-within-k-stops.py @@ -0,0 +1,17 @@ +""" +Bellman-Ford. +Time: O(KE) +""" +class Solution: + def findCheapestPrice(self, N: int, flights: List[List[int]], src: int, dst: int, K: int) -> int: + prices = {n:float('inf') for n in range(N)} + prices[src] = 0 + + for k in range(K+1): + temp = prices.copy() + for source, destination, price in flights: + if prices[source]==float('inf'): continue + if prices[source]+price int: + dp = [0]*(N+1) + dp[0] = 1 + for i in range(len(dp)): + if i-1>=0: + dp[i] += dp[i-1] + if i-2>=0: + dp[i] += dp[i-2] + return dp[-1] \ No newline at end of file diff --git a/problems/python3/clone-graph.py b/problems/python3/clone-graph.py new file mode 100644 index 0000000..9a6765f --- /dev/null +++ b/problems/python3/clone-graph.py @@ -0,0 +1,16 @@ +class Solution: + def cloneGraph(self, start: 'Node') -> 'Node': + def dfs(node): + if node in clones: return clones[node] + + copy = Node(node.val) + clones[node] = copy + + for neighbor in node.neighbors: + copy.neighbors.append(dfs(neighbor)) + + return clones[node] + if not start: return start + clones = {} + dfs(start) + return clones[start] \ No newline at end of file diff --git a/problems/python3/coin-change-ii.py b/problems/python3/coin-change-ii.py new file mode 100644 index 0000000..8b4ad26 --- /dev/null +++ b/problems/python3/coin-change-ii.py @@ -0,0 +1,12 @@ +class Solution: + def change(self, amount: int, coins: List[int]) -> int: + dp = [0]*(amount+1) + dp[0] = 1 + + coins.sort() + + for coin in coins: + for a in range(1, amount+1): + if a-coin<0: continue + dp[a] += dp[a-coin] + return dp[-1] \ No newline at end of file diff --git a/problems/python3/coin-change.py b/problems/python3/coin-change.py new file mode 100644 index 0000000..19571e4 --- /dev/null +++ b/problems/python3/coin-change.py @@ -0,0 +1,14 @@ +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + dp = [float('inf')]*(amount+1) + dp[0] = 0 + for coin in coins: + if coin List[List[int]]: + def helper(i, target): + if target==0: + ans.append(combination.copy()) + return + + if i==len(candidates) or candidates[i]>target: + return + + combination.append(candidates[i]) + helper(i+1, target-candidates[i]) + combination.pop() + + while i+1 List[List[int]]: + def helper(i, currSum, target): + if currSum>target: + return + + if currSum==target: + ans.append(combination.copy()) + return + + for j in range(i, len(candidates)): + combination.append(candidates[j]) + helper(j, currSum+candidates[j], target) + combination.pop() + + ans = [] + combination = [] + helper(0, 0, target) + return ans \ No newline at end of file diff --git a/problems/python3/construct-binary-tree-from-preorder-and-inorder-traversal.py b/problems/python3/construct-binary-tree-from-preorder-and-inorder-traversal.py new file mode 100644 index 0000000..eb7f19c --- /dev/null +++ b/problems/python3/construct-binary-tree-from-preorder-and-inorder-traversal.py @@ -0,0 +1,34 @@ +""" +preorder: [root][left][right] +inorder: [left][root][right] + +[0] +As you can see, the root is always located at the first index of the preorder list. + +[2] +Using the same logic, we can recursively get the left and right node +Inorder to do that, we need to define left and right nodes' range. +And we can get the length of the left subtree by locating the index of the root in the in order list. [1] +i and j is the startIndex and the (endIndex+1) of the preorder list. +k and l is the startIndex and the (endIndex+1) of the inorder list. + +Time: O(N) +Space: O(N) +""" +class Solution: + def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: + def helper(i, j, k, l): + if i==j or k==l: return None + + root = TreeNode(preorder[i]) #[0] + + rootInorderIndex = getInorderIndex[root.val] #[1] + leftLen = rootInorderIndex-k + + root.left = helper(i+1, i+1+leftLen, k, k+leftLen) #[2] + root.right = helper(i+1+leftLen, j, rootInorderIndex+1, l) + return root + + getInorderIndex = {} + for i, v in enumerate(inorder): getInorderIndex[v] = i + return helper(0, len(preorder), 0, len(inorder)) \ No newline at end of file diff --git a/problems/python3/container-with-most-water.py b/problems/python3/container-with-most-water.py new file mode 100755 index 0000000..a560441 --- /dev/null +++ b/problems/python3/container-with-most-water.py @@ -0,0 +1,42 @@ +""" +Time: O(N) +Space: O(1) + +i and j starts from the leftest and rightest. Move the one that has less height. + +Why we won't miss any i and j? +For example, currently i is heigher than j. +If between i~j, there are no height that is larger or equal to i, than since the area is `min(height[i], height[j]) * (j-i)`, you cannot find any area that is larger than the current one. +If between i~j, there is a height that is larger or equal to i, j will on it, and it will be tested. +Thus, given any i and j, any other future i and j that have the potential of forming larger area will be tested. +""" +class Solution: + def maxArea(self, height: List[int]) -> int: + i = 0 + j = len(height)-1 + ans = 0 + + while iheight[j]: + j -= 1 + else: + i += 1 + return ans + + + +class Solution: + def maxArea(self, height: List[int]) -> int: + i = 0 + j = len(height)-1 + ans = 0 + + while iheight[j]: + j -= 1 + else: + i += 1 + return ans \ No newline at end of file diff --git a/problems/python3/contains-duplicate.py b/problems/python3/contains-duplicate.py new file mode 100644 index 0000000..7849e84 --- /dev/null +++ b/problems/python3/contains-duplicate.py @@ -0,0 +1,7 @@ +class Solution: + def containsDuplicate(self, nums: List[int]) -> bool: + seen = set() + for num in nums: + if num in seen: return True + seen.add(num) + return False \ No newline at end of file diff --git a/problems/python3/copy-list-with-random-pointer.py b/problems/python3/copy-list-with-random-pointer.py new file mode 100644 index 0000000..4436b78 --- /dev/null +++ b/problems/python3/copy-list-with-random-pointer.py @@ -0,0 +1,34 @@ +""" +Time: O(N) +Space: O(1) + +The easiest way would be maintaining a hash map for original node to the copy and the other way around. Then the rest is easy. +But this will take us O(N) of extra space. + +Two pass solution with constant space. +First pass. +Create a copy of the original and store it inside "random". +The copy points to original's next and original's random. + +Second pass. +Iterate through the nodes again. +This time we adjust the copy to point to the other copies. +""" +class Solution: + def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': + if not head: return head + + node = head + while node: + copy = Node(node.val, node.next, node.random) + node.random = copy + node = node.next + + node = head + while node: + newNode = node.random + if node.next: newNode.next = node.next.random + if newNode.random: newNode.random = newNode.random.random + node = node.next + + return head.random \ No newline at end of file diff --git a/problems/python3/count-good-nodes-in-binary-tree.py b/problems/python3/count-good-nodes-in-binary-tree.py new file mode 100755 index 0000000..2288d12 --- /dev/null +++ b/problems/python3/count-good-nodes-in-binary-tree.py @@ -0,0 +1,16 @@ +""" +Time: O(N) +Space: O(LogN) for recursion stack if the tree is balanced. +""" +class Solution: + def goodNodes(self, root: TreeNode) -> int: + def helper(node, maxVal): + nonlocal count + if not node: return + if node.val>=maxVal: count += 1 + helper(node.left, max(maxVal, node.val)) + helper(node.right, max(maxVal, node.val)) + + count = 0 + helper(root, float('-inf')) + return count \ No newline at end of file diff --git a/problems/python3/counting-bits.py b/problems/python3/counting-bits.py new file mode 100644 index 0000000..7325596 --- /dev/null +++ b/problems/python3/counting-bits.py @@ -0,0 +1,9 @@ +class Solution: + def countBits(self, n: int) -> List[int]: + ans = [0]*(n+1) + offset = 1 + + for i in range(1, n+1): + if offset*2==i: offset = offset*2 + ans[i] = 1+ans[i-offset] + return ans \ No newline at end of file diff --git a/problems/python3/course-schedule-ii.py b/problems/python3/course-schedule-ii.py new file mode 100644 index 0000000..00725b4 --- /dev/null +++ b/problems/python3/course-schedule-ii.py @@ -0,0 +1,30 @@ +#Topological Sort +class Solution: + def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: + sortedCourse = [] + G = collections.defaultdict(list) #graph + inbounds = collections.Counter() + q = collections.deque() + + #build graph + for c1, c2 in prerequisites: + G[c2].append(c1) + inbounds[c1] += 1 + + #add the starting point to the q. (the ones that have 0 inbounds) + for course in range(numCourses): + if inbounds[course]==0: q.append(course) + + #add the course that have 0 inbounds to the sortedCourse. + #after that, imagine we remove it from the graph, so nextCourse inbound will -1 + #add to q if nextCourse have 0 inbounds + while q: + course = q.popleft() + + sortedCourse.append(course) + + for nextCourse in G[course]: + inbounds[nextCourse] -= 1 + if inbounds[nextCourse]==0: q.append(nextCourse) + + return sortedCourse if len(sortedCourse)==numCourses else [] \ No newline at end of file diff --git a/problems/python3/course-schedule.py b/problems/python3/course-schedule.py new file mode 100644 index 0000000..f52d07d --- /dev/null +++ b/problems/python3/course-schedule.py @@ -0,0 +1,23 @@ +class Solution: + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + sortedCourse = [] + inbounds = collections.Counter() + G = collections.defaultdict(list) + q = collections.deque() + + for c1, c2 in prerequisites: + G[c2].append(c1) + inbounds[c1] += 1 + + for c in range(numCourses): + if inbounds[c]==0: q.append(c) + + while q: + c = q.popleft() + + for c2 in G[c]: + inbounds[c2] -= 1 + if inbounds[c2]==0: q.append(c2) + sortedCourse.append(c) + + return len(sortedCourse)==numCourses \ No newline at end of file diff --git a/problems/python3/daily-temperatures.py b/problems/python3/daily-temperatures.py new file mode 100644 index 0000000..d6e4c98 --- /dev/null +++ b/problems/python3/daily-temperatures.py @@ -0,0 +1,12 @@ +class Solution: + def dailyTemperatures(self, temperatures: List[int]) -> List[int]: + ans = [0]*len(temperatures) + stack = [] + + for i, temp in enumerate(temperatures): + while stack and stack[-1][0] int: + mapping = set([str(n) for n in range(1, 27)]) + N = len(s) + dp = [0]*(N+1) + dp[0] = 1 + + for i in range(1, N+1): + if i-1>=0 and s[i-1] in mapping: dp[i] += dp[i-1] + if i-2>=0 and s[i-2:i] in mapping: dp[i] += dp[i-2] + return dp[-1] \ No newline at end of file diff --git a/problems/python3/design-add-and-search-words-data-structure.py b/problems/python3/design-add-and-search-words-data-structure.py new file mode 100644 index 0000000..52effc8 --- /dev/null +++ b/problems/python3/design-add-and-search-words-data-structure.py @@ -0,0 +1,33 @@ +class WordDictionary: + + def __init__(self): + self.root = {} + + def addWord(self, word: str) -> None: + node = self.root + for c in word: + if c not in node: node[c] = {} + node = node[c] + node['$'] = {} #'.' means the end of the word + + def search(self, word: str) -> bool: + def helper(node, i): + if i==len(word): return '$' in node + + if word[i]=='.': + for c in node: + if helper(node[c], i+1): return True + return False + else: + if word[i] not in node: return False + return helper(node[word[i]], i+1) + return helper(self.root, 0) + + + + + + + + + \ No newline at end of file diff --git a/problems/python3/design-twitter.py b/problems/python3/design-twitter.py new file mode 100644 index 0000000..a2781ae --- /dev/null +++ b/problems/python3/design-twitter.py @@ -0,0 +1,49 @@ +class Twitter: + + def __init__(self): + self.followData = collections.defaultdict(set) + self.tweetData = collections.defaultdict(list) + self.count = 0 + + + def postTweet(self, userId: int, tweetId: int) -> None: + self.tweetData[userId].append((self.count, tweetId)) + self.count -= 1 + + + def getNewsFeed(self, userId: int) -> List[int]: + newsFeed = [] + h = [] + + self.followData[userId].add(userId) + for followeeId in self.followData[userId]: + if followeeId not in self.tweetData: continue + index = len(self.tweetData[followeeId])-1 + count, tweetId = self.tweetData[followeeId][index] + h.append((count, tweetId, followeeId, index-1)) + heapq.heapify(h) + + while h and len(newsFeed)<10: + _, tweetId, userId, index = heapq.heappop(h) + newsFeed.append(tweetId) + if index>=0: + count, tweetId2 = self.tweetData[userId][index] + heapq.heappush(h, (count, tweetId2, userId, index-1)) + return newsFeed + + + + def follow(self, followerId: int, followeeId: int) -> None: + self.followData[followerId].add(followeeId) + + def unfollow(self, followerId: int, followeeId: int) -> None: + if followerId not in self.followData: return + self.followData[followerId].remove(followeeId) + + +# Your Twitter object will be instantiated and called as such: +# obj = Twitter() +# obj.postTweet(userId,tweetId) +# param_2 = obj.getNewsFeed(userId) +# obj.follow(followerId,followeeId) +# obj.unfollow(followerId,followeeId) \ No newline at end of file diff --git a/problems/python3/detect-squares.py b/problems/python3/detect-squares.py new file mode 100644 index 0000000..af86946 --- /dev/null +++ b/problems/python3/detect-squares.py @@ -0,0 +1,22 @@ +class DetectSquares: + + def __init__(self): + self.store = collections.Counter() + + def add(self, point: List[int]) -> None: + self.store[tuple(point)] += 1 + + def count(self, point: List[int]) -> int: + x, y = point + ans = 0 + + for dx, dy in self.store: + if abs(x-dx)!=abs(y-dy) or x==dx or y==dy: continue + ans += self.store[(dx, dy)]*self.store[(dx, y)]*self.store[(x, dy)] + return ans + + +# Your DetectSquares object will be instantiated and called as such: +# obj = DetectSquares() +# obj.add(point) +# param_2 = obj.count(point) \ No newline at end of file diff --git a/problems/python3/diameter-of-binary-tree.py b/problems/python3/diameter-of-binary-tree.py new file mode 100755 index 0000000..1f9704d --- /dev/null +++ b/problems/python3/diameter-of-binary-tree.py @@ -0,0 +1,17 @@ +""" +Time: O(N) +Space: O(LogN) for the recursion stack. If the tree is balanced. +""" +class Solution: + def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: + def helper(node): + if not node: return 0 + l = helper(node.left) + r = helper(node.right) + self.ans = max(self.ans, 1+l+r) + return 1+max(l, r) + + self.ans = 0 + + helper(root) + return self.ans-1 \ No newline at end of file diff --git a/problems/python3/distinct-subsequences.py b/problems/python3/distinct-subsequences.py new file mode 100644 index 0000000..d84fcf6 --- /dev/null +++ b/problems/python3/distinct-subsequences.py @@ -0,0 +1,17 @@ +class Solution: + def numDistinct(self, s: str, t: str) -> int: + def dfs(i, j): + if (i, j) in visited: return visited[(i, j)] + + if j>=len(t): return 1 + if i>=len(s): return 0 + + if s[i]==t[j]: + visited[(i, j)] = dfs(i+1, j+1)+dfs(i+1, j) + else: + visited[(i, j)] = dfs(i+1, j) + return visited[(i, j)] + + visited = {} + dfs(0, 0) + return dfs(0, 0) \ No newline at end of file diff --git a/problems/python3/edit-distance.py b/problems/python3/edit-distance.py new file mode 100644 index 0000000..c8fa8f4 --- /dev/null +++ b/problems/python3/edit-distance.py @@ -0,0 +1,37 @@ +""" +dfs(i, j) +i being the unprocessed index in word1. +j, word2. + +MAIN LOGIC: +if word1[i]==word2[j], no operation need, return dfs(i+1, j+1) +if not, need 1 operation, so +replace: dfs(i+1, j+1) +insert: dfs(i, j+1) +delete: dfs(i+1, j) + +BASE CASE: +If both string are empty (i==N and j==M), no operation needed. +If one string are empty, then the remain operation is the length of the non-empty one. +""" +class Solution: + def minDistance(self, word1: str, word2: str) -> int: + N = len(word1) + M = len(word2) + def dfs(i, j)->int: + if i==N and j==M: return 0 + if i==N: return M-j + if j==M: return N-i + + if (i, j) in history: + return history[(i, j)] + + if word1[i]==word2[j]: + history[(i, j)] = dfs(i+1, j+1) + else: + history[(i, j)] = 1+min(dfs(i+1, j+1), dfs(i+1, j), dfs(i, j+1)) + + return history[(i, j)] + + history = {} + return dfs(0, 0) \ No newline at end of file diff --git a/problems/python3/encode-and-decode-strings.py b/problems/python3/encode-and-decode-strings.py new file mode 100644 index 0000000..f38c4e4 --- /dev/null +++ b/problems/python3/encode-and-decode-strings.py @@ -0,0 +1,23 @@ +class Codec: + def encode(self, strs: List[str]) -> str: + output = '' + for string in strs: + output += str(len(string))+'#'+string + return output + + + def decode(self, s: str) -> List[str]: + output = [] + i = 0 + + while i int: + operators = set(['+', '-', '*', '/']) + stack = [] + + for c in tokens: + if c in operators: + n2 = stack.pop() + n1 = stack.pop() + + if c=='+': + stack.append(n1+n2) + elif c=='-': + stack.append(n1-n2) + elif c=='*': + stack.append(n1*n2) + elif c=='/': + stack.append(int(n1/n2)) + else: + stack.append(int(c)) + + return stack.pop() \ No newline at end of file diff --git a/problems/python3/find-median-from-data-stream.py b/problems/python3/find-median-from-data-stream.py new file mode 100644 index 0000000..f83ee40 --- /dev/null +++ b/problems/python3/find-median-from-data-stream.py @@ -0,0 +1,32 @@ +class MedianFinder: + + def __init__(self): + self.large = [] #store nums larger or equal to the median + self.small = [] #store nums samaller to the median + + def addNum(self, num: int) -> None: + if not self.large and not self.small: + heapq.heappush(self.large, num) + elif num>=self.findMedian(): + heapq.heappush(self.large, num) + self.balance() + else: + heapq.heappush(self.small, -num) + self.balance() + + def balance(self) -> None: + #make the length of two heaps as even as posible + if len(self.large)>len(self.small)+1: + num = heapq.heappop(self.large) + heapq.heappush(self.small, -num) + + if len(self.small)>len(self.large): + num = -heapq.heappop(self.small) + heapq.heappush(self.large, num) + + + def findMedian(self) -> float: + if (len(self.large)+len(self.small))%2==0: + return (self.large[0]-self.small[0])/2 + else: + return self.large[0] \ No newline at end of file diff --git a/problems/python3/find-minimum-in-rotated-sorted-array.py b/problems/python3/find-minimum-in-rotated-sorted-array.py new file mode 100644 index 0000000..c9af305 --- /dev/null +++ b/problems/python3/find-minimum-in-rotated-sorted-array.py @@ -0,0 +1,22 @@ +""" +Usually the binary search problem, the hard part is to clear all the edge cases. +What will be the edge case? It will be when the recursive function executed at the deepest level. Usually the list with only one or two element. +So I will suggest before submit, try out the case like [0,1] or [1,0] to make sure it will not run unstop. +""" +class Solution: + def findMin(self, nums: List[int]) -> int: + def helper(l, r): + nonlocal ans + if l>r: return + + m = l + int((r-l)/2) + if nums[l]<=nums[m]: + ans = min(ans, nums[l]) + helper(m+1, r) + else: + ans = min(ans, nums[m+1]) + helper(l, m) + + ans = float('inf') + helper(0, len(nums)-1) + return ans \ No newline at end of file diff --git a/problems/python3/find-the-duplicate-number.py b/problems/python3/find-the-duplicate-number.py new file mode 100644 index 0000000..41636df --- /dev/null +++ b/problems/python3/find-the-duplicate-number.py @@ -0,0 +1,16 @@ +class Solution: + def findDuplicate(self, nums: List[int]) -> int: + head = nums[0] + slow = head + fast = head + + while True: + slow = nums[slow] + fast = nums[nums[fast]] + if slow==fast: break + + slow = head + while slow!=fast: + slow = nums[slow] + fast = nums[fast] + return slow \ No newline at end of file diff --git a/problems/python3/gas-station.py b/problems/python3/gas-station.py new file mode 100644 index 0000000..017eaa6 --- /dev/null +++ b/problems/python3/gas-station.py @@ -0,0 +1,23 @@ +""" +First thing, you must understand that if sum(gas)>=sum(cost) there must be an answer. Guaranteed. +If we know there is an answer, we can simply test all the index. +If the currGas ever drops to below 0, it means that we need to switch a "start". + +Time: O(N) +Space: O(1) +""" +class Solution: + def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int: + if sum(gas) List[str]: + def helper(parentheses, left, right): + if len(parentheses)==n*2: + ans.append("".join(parentheses)) + return + + if leftright: + parentheses.append(')') + helper(parentheses, left, right+1) + parentheses.pop() + + ans = [] + helper([], 0, 0) + return ans \ No newline at end of file diff --git a/problems/python3/graph-valid-tree.py b/problems/python3/graph-valid-tree.py new file mode 100644 index 0000000..2168e9a --- /dev/null +++ b/problems/python3/graph-valid-tree.py @@ -0,0 +1,33 @@ +class Solution: + def validTree(self, N: int, edges: List[List[int]]) -> bool: + def union(n1, n2) -> bool: + p1 = find(n1) + p2 = find(n2) + + if p1==p2: + return False + elif p1 int: + p = parents[n] + while p!=parents[p]: p = find(p) + parents[n] = p + return p + + parents = [n for n in range(N)] + + for n1, n2 in edges: + if not union(n1, n2): return False + + #check if all node trace back to the same root + root = find(0) + for n in range(1, N): + if root!=find(n): return False + + return True \ No newline at end of file diff --git a/problems/python3/group-anagrams.py b/problems/python3/group-anagrams.py new file mode 100644 index 0000000..7430f48 --- /dev/null +++ b/problems/python3/group-anagrams.py @@ -0,0 +1,23 @@ +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + def normalize(string: str) -> str: + counter = collections.Counter() + ans = '' + + for c in string: + counter[c] += 1 + + for c in 'abcdefghijklmnopqrstuvwxyz': + if counter[c]>0: + ans += c+str(counter[c]) + return ans + + group = collections.defaultdict(list) + ans = [] + + for string in strs: + group[normalize(string)].append(string) + + for normalizedString in group: + ans.append(group[normalizedString]) + return ans \ No newline at end of file diff --git a/problems/python3/hand-of-straights.py b/problems/python3/hand-of-straights.py new file mode 100644 index 0000000..f20c32d --- /dev/null +++ b/problems/python3/hand-of-straights.py @@ -0,0 +1,18 @@ +class Solution: + def isNStraightHand(self, hand: List[int], groupSize: int) -> bool: + if len(hand)%groupSize!=0: return False + + counter = collections.Counter(hand) + h = list(counter.keys()) + heapq.heapify(h) + + while h: + minNum = h[0] + for n in range(minNum, minNum+groupSize): + if counter[n]<=0: return False + counter[n] -= 1 + if counter[n]==0: + if h[0]!=n: return False + heapq.heappop(h) + + return True \ No newline at end of file diff --git a/problems/python3/happy-number.py b/problems/python3/happy-number.py new file mode 100644 index 0000000..7756197 --- /dev/null +++ b/problems/python3/happy-number.py @@ -0,0 +1,17 @@ +class Solution: + def isHappy(self, n: int) -> bool: + def digitSquare(n) -> int: + ans = 0 + while n>0: + ans += (n%10)**2 + n = n//10 + return ans + + visited = set() + visited.add(1) + + while n not in visited: + visited.add(n) + n = digitSquare(n) + + return n==1 diff --git a/problems/python3/house-robber-ii.py b/problems/python3/house-robber-ii.py new file mode 100644 index 0000000..1ed9b82 --- /dev/null +++ b/problems/python3/house-robber-ii.py @@ -0,0 +1,18 @@ +class Solution: + def rob(self, nums: List[int]) -> int: + if len(nums)<=1: return max(nums) + + N = len(nums) + dp = [[0, 0] for _ in range(N)] + dp[0][0] = nums[0] + + for i in range(1, N): + dp[i][0] = nums[i]+dp[i-1][1] + dp[i][1] = max(dp[i-1]) + + dp2 = [[0, 0] for _ in range(N)] + for i in range(1, N): + dp2[i][0] = nums[i]+dp2[i-1][1] + dp2[i][1] = max(dp2[i-1]) + + return max(dp[-1][1], dp2[-1][0]) \ No newline at end of file diff --git a/problems/python3/house-robber.py b/problems/python3/house-robber.py new file mode 100644 index 0000000..fbd86a5 --- /dev/null +++ b/problems/python3/house-robber.py @@ -0,0 +1,17 @@ +""" +Time: O(N) +Space: O(N), can reduece to O(1). + +dp[i][0] := max revenue if house i robbed +dp[i][1] := max revenue if house i not robbed +""" +class Solution: + def rob(self, nums: List[int]) -> int: + N = len(nums) + dp = [[0, 0] for _ in range(N)] + dp[0][0] = nums[0] + + for i in range(1, N): + dp[i][0] = nums[i]+dp[i-1][1] + dp[i][1] = max(dp[i-1]) + return max(dp[-1]) \ No newline at end of file diff --git a/problems/python3/implement-trie-prefix-tree.py b/problems/python3/implement-trie-prefix-tree.py new file mode 100644 index 0000000..1118e97 --- /dev/null +++ b/problems/python3/implement-trie-prefix-tree.py @@ -0,0 +1,26 @@ +class Trie: + + def __init__(self): + self.root = {} + + def insert(self, word: str) -> None: + node = self.root + for c in word: + if c not in node: node[c] = {} + node = node[c] + node['.'] = {} #'.' means the end of the word + + + def search(self, word: str) -> bool: + node = self.root + for c in word: + if c not in node: return False + node = node[c] + return '.' in node + + def startsWith(self, prefix: str) -> bool: + node = self.root + for c in prefix: + if c not in node: return False + node = node[c] + return True diff --git a/problems/python3/insert-interval.py b/problems/python3/insert-interval.py new file mode 100644 index 0000000..667ba04 --- /dev/null +++ b/problems/python3/insert-interval.py @@ -0,0 +1,23 @@ +class Solution: + def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]: + ans = [] + i = 0 + + #add intervals before newInterval + while i bool: + def dfs(i, j): + if (i, j) in history: return False + if i+j==len(s3): return True + if i Optional[TreeNode]: + if not root: return root + left = root.left + right = root.right + root.left = self.invertTree(right) + root.right = self.invertTree(left) + return root + + +""" +Iterative +Time: O(N) +Space: O(N) +""" +class Solution: + def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: + if not root: return root + q = collections.deque([root]) + + while q: + node = q.popleft() + left = node.left + right = node.right + node.right = left + node.left = right + + if node.left: q.append(node.left) + if node.right: q.append(node.right) + + return root \ No newline at end of file diff --git a/problems/python3/jump-game-ii.py b/problems/python3/jump-game-ii.py new file mode 100644 index 0000000..35be09f --- /dev/null +++ b/problems/python3/jump-game-ii.py @@ -0,0 +1,22 @@ +""" +l and r is the index range we can reach within "steps". +So while r can not reach the end (`r int: + l = r = 0 + steps = 0 + + while r bool: + maxIndex = 0 + + for i, num in enumerate(nums): + if maxIndex List[List[int]]: + h = [] + + for x, y in points: + dis = (x**2+y**2)**0.5 + heapq.heappush(h, (-dis, x, y)) + if len(h)>k: heapq.heappop(h) + + return [(x, y) for dis, x, y in h] \ No newline at end of file diff --git a/problems/python3/koko-eating-bananas.py b/problems/python3/koko-eating-bananas.py new file mode 100644 index 0000000..fe03197 --- /dev/null +++ b/problems/python3/koko-eating-bananas.py @@ -0,0 +1,21 @@ +class Solution: + def minEatingSpeed(self, piles: List[int], h: int) -> int: + def canFinish(k): + timeNeeded = 0 + for pile in piles: + timeNeeded += math.ceil(pile/k) + return timeNeeded<=h + + kMin = 1 + kMax = max(piles) + ans = kMax + + while kMin<=kMax: + k = kMin + int((kMax-kMin)/2) + if canFinish(k): + ans = min(ans, k) + kMax = k-1 + else: + kMin = k+1 + + return ans \ No newline at end of file diff --git a/problems/python3/kth-largest-element-in-a-stream.py b/problems/python3/kth-largest-element-in-a-stream.py new file mode 100644 index 0000000..1eb9095 --- /dev/null +++ b/problems/python3/kth-largest-element-in-a-stream.py @@ -0,0 +1,14 @@ +class KthLargest: + + def __init__(self, k: int, nums: List[int]): + self.k = k + self.nums = nums + + heapq.heapify(self.nums) + while len(self.nums)>self.k: heapq.heappop(self.nums) + + + def add(self, val: int) -> int: + heapq.heappush(self.nums, val) + if len(self.nums)>self.k: heapq.heappop(self.nums) + return self.nums[0] \ No newline at end of file diff --git a/problems/python3/kth-smallest-element-in-a-bst.py b/problems/python3/kth-smallest-element-in-a-bst.py new file mode 100755 index 0000000..c8a1555 --- /dev/null +++ b/problems/python3/kth-smallest-element-in-a-bst.py @@ -0,0 +1,21 @@ +""" +Time: O(N) for the inorder traversal +Space: O(LogN) if the tree is balanced. +""" +class Solution: + def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: + count = 0 + stack = [] + node = root + + while stack or node: + while node: + stack.append(node) + node = node.left + node = stack.pop() + + count += 1 + if count==k: return node.val + + node = node.right + return 0 \ No newline at end of file diff --git a/problems/python3/largest-rectangle-in-histogram.py b/problems/python3/largest-rectangle-in-histogram.py new file mode 100644 index 0000000..1cf8508 --- /dev/null +++ b/problems/python3/largest-rectangle-in-histogram.py @@ -0,0 +1,21 @@ +""" +[0] For each height, if the it is lower than the previous one, it means that the previous are not able to extend anymore. So we calculate its area. + +[1] If the previous area, is larger than the current one, it means that the current one are able to extand backward. +""" +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + maxArea = 0 + stack = [] + + heights.append(0) #dummy for the ending + + for i, h in enumerate(heights): + start = i + while stack and h int: + stones = [-stone for stone in stones] + heapq.heapify(stones) + + while len(stones)>=2: + w1 = -heapq.heappop(stones) + w2 = -heapq.heappop(stones) + + if w1-w2>0: heapq.heappush(stones, -(w1-w2)) + + return -stones[0] if stones else 0 \ No newline at end of file diff --git a/problems/python3/letter-combinations-of-a-phone-number.py b/problems/python3/letter-combinations-of-a-phone-number.py new file mode 100644 index 0000000..582106c --- /dev/null +++ b/problems/python3/letter-combinations-of-a-phone-number.py @@ -0,0 +1,25 @@ +""" +Time: O(4^N * N), there are around 4^N of combination. Each taking O(N) to form. +Space: O(N). O(N) for recursion stack. O(N). O(N) for `combination`. O(N+N) ~= O(N). +""" +class Solution: + def letterCombinations(self, digits: str) -> List[str]: + def helper(i): + if i==len(digits): + ans.append(''.join(combination)) + return + + for c in mapping[digits[i]]: + combination.append(c) + helper(i+1) + combination.pop() + + mapping = {'2': ('a', 'b', 'c'), '3': ('d', 'e', 'f'), + '4': ('g', 'h', 'i'), '5': ('j', 'k', 'l'), '6': ('m', 'n', 'o'), + '7': ('p', 'q', 'r', 's'), '8': ('t', 'u', 'v'), '9': ('w', 'x', 'y', 'z')} + + if not digits: return [] + ans = [] + combination = [] + helper(0) + return ans \ No newline at end of file diff --git a/problems/python3/linked-list-cycle.py b/problems/python3/linked-list-cycle.py new file mode 100644 index 0000000..d2e5395 --- /dev/null +++ b/problems/python3/linked-list-cycle.py @@ -0,0 +1,16 @@ +class Solution: + def hasCycle(self, head: Optional[ListNode]) -> bool: + if not head: return False + + slow = head + fast = head + + while fast: + slow = slow.next + + if not fast.next: return False + fast = fast.next.next + + if slow==fast: return True + + return False \ No newline at end of file diff --git a/problems/python3/longest-common-subsequence.py b/problems/python3/longest-common-subsequence.py new file mode 100644 index 0000000..5ba3a68 --- /dev/null +++ b/problems/python3/longest-common-subsequence.py @@ -0,0 +1,16 @@ +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + #dp[i][j] := number of longest Common Subsequence with text2[:i] and text2[:j] + + N = len(text1) + M = len(text2) + + dp = [[0]*(M+1) for _ in range(N+1)] + + for i in range(1, N+1): + for j in range(1, M+1): + if text1[i-1]==text2[j-1]: + dp[i][j] = dp[i-1][j-1]+1 + else: + dp[i][j] = max(dp[i][j-1], dp[i-1][j]) + return dp[-1][-1] \ No newline at end of file diff --git a/problems/python3/longest-consecutive-sequence.py b/problems/python3/longest-consecutive-sequence.py new file mode 100644 index 0000000..dec2cfa --- /dev/null +++ b/problems/python3/longest-consecutive-sequence.py @@ -0,0 +1,15 @@ +class Solution: + def longestConsecutive(self, nums: List[int]) -> int: + numSet = set(nums) + ans = 0 + + for num in nums: + isStart = num-1 not in numSet + if isStart: + count = 0 + temp = num + while temp in numSet: + count += 1 + temp += 1 + ans = max(count, ans) + return ans \ No newline at end of file diff --git a/problems/python3/longest-increasing-path-in-a-matrix.py b/problems/python3/longest-increasing-path-in-a-matrix.py new file mode 100644 index 0000000..99fed7d --- /dev/null +++ b/problems/python3/longest-increasing-path-in-a-matrix.py @@ -0,0 +1,26 @@ +""" +Time: O(MN) since the memo at most has MN index. +Space: O(MN) +""" +class Solution: + def longestIncreasingPath(self, matrix: List[List[int]]) -> int: + def dfs(i0, j0): + if (i0, j0) in memo: return memo[(i0, j0)] + ans = 1 + + for i, j in ((i0+1, j0), (i0-1, j0), (i0, j0+1),(i0, j0-1)): + if i<0 or i>=N or j<0 or j>=M: continue + if matrix[i][j]<=matrix[i0][j0]: continue + ans = max(ans, 1+dfs(i, j)) + + memo[(i0, j0)] = ans + return ans + + N = len(matrix) + M = len(matrix[0]) + memo = {} + ans = 0 + for i in range(N): + for j in range(M): + ans = max(ans, dfs(i, j)) + return ans \ No newline at end of file diff --git a/problems/python3/longest-palindromic-substring.py b/problems/python3/longest-palindromic-substring.py new file mode 100644 index 0000000..9edb477 --- /dev/null +++ b/problems/python3/longest-palindromic-substring.py @@ -0,0 +1,45 @@ +""" +Time: O(N^2) +Space: O(N^2) + +DP, TLE +""" +class Solution: + def longestPalindrome(self, s: str) -> str: + ans = s[0] + N = len(s) + dp = [[False]*N for _ in range(N)] + + for i in range(N): dp[i][i] = True + + for l in range(2, N+1): + for i in range(N): + j = i+l-1 + if j>=N: continue + dp[i][j] = s[i]==s[j] and (dp[i+1][j-1] or j-1 str: + N = len(s) + ans = s[0] + + for i in range(N): + l, r = i, i + while l>=0 and rlen(ans): ans = s[l:r+1] + l -= 1 + r += 1 + + l, r = i, i+1 + while l>=0 and rlen(ans): ans = s[l:r+1] + l -= 1 + r += 1 + return ans \ No newline at end of file diff --git a/problems/python3/longest-repeating-character-replacement.py b/problems/python3/longest-repeating-character-replacement.py new file mode 100644 index 0000000..714d8f1 --- /dev/null +++ b/problems/python3/longest-repeating-character-replacement.py @@ -0,0 +1,13 @@ +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + counter = collections.Counter() + l = 0 + ans = 0 + + for r in range(len(s)): + counter[s[r]] += 1 + while (r-l+1)-max(counter.values()) > k: + counter[s[l]] -= 1 + l += 1 + ans = max(ans, r-l+1) + return ans \ No newline at end of file diff --git a/problems/python3/longest-substring-without-repeating-char.py b/problems/python3/longest-substring-without-repeating-char.py new file mode 100644 index 0000000..9902bb5 --- /dev/null +++ b/problems/python3/longest-substring-without-repeating-char.py @@ -0,0 +1,14 @@ +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + ans = 0 + l = 0 + seen = set() + + for r in range(len(s)): + while s[r] in seen: + seen.remove(s[l]) + l += 1 + seen.add(s[r]) + ans = max(ans, r-l+1) + + return ans \ No newline at end of file diff --git a/problems/python3/lowest-common-ancestor-of-a-binary-search-tree.py b/problems/python3/lowest-common-ancestor-of-a-binary-search-tree.py new file mode 100755 index 0000000..12e1743 --- /dev/null +++ b/problems/python3/lowest-common-ancestor-of-a-binary-search-tree.py @@ -0,0 +1,28 @@ +""" +Recursive +Time: O(LogN) if the tree is balanced. +Space: O(LogN) for the recursion stack. +""" +class Solution: + def lowestCommonAncestor(self, node: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + if q.val<=node.val<=p.val or p.val<=node.val<=q.val: + return node + elif q.val 'TreeNode': + while not (q.val<=node.val<=p.val or p.val<=node.val<=q.val): + if q.val int: + if key in self.dic: + node = self.dic[key] + self.remove(node) + self.promote(node) + return node.val + return -1 + + def put(self, key: int, value: int) -> None: + if key in self.dic: + self.remove(self.dic[key]) + node = Node(key, value) + self.promote(node) + self.dic[key] = node + + if len(self.dic)>self.capacity: #[2] + del self.dic[self.tail.prev.key] + self.remove(self.tail.prev) diff --git a/problems/python3/max-area-of-island.py b/problems/python3/max-area-of-island.py new file mode 100644 index 0000000..fd55a94 --- /dev/null +++ b/problems/python3/max-area-of-island.py @@ -0,0 +1,23 @@ +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + def dfs(i, j) -> int: + if i<0 or j<0 or i>=MAX_ROW or j>=MAX_COL: return 0 + if grid[i][j]==0 or grid[i][j]==2: return 0 + + grid[i][j] = 2 #mark as visited + + area = 1 + area += dfs(i+1, j) + area += dfs(i-1, j) + area += dfs(i, j+1) + area += dfs(i, j-1) + return area + + ans = 0 + MAX_ROW = len(grid) + MAX_COL = len(grid[0]) + + for i in range(MAX_ROW): + for j in range(MAX_COL): + ans = max(ans, dfs(i, j)) + return ans \ No newline at end of file diff --git a/problems/python3/maximum-depth-of-binary-tree.py b/problems/python3/maximum-depth-of-binary-tree.py new file mode 100755 index 0000000..3146dcf --- /dev/null +++ b/problems/python3/maximum-depth-of-binary-tree.py @@ -0,0 +1,8 @@ +""" +Time: O(N) +Space:O(LogN), for recursion stack space. +""" +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if not root: return 0 + return 1+max(self.maxDepth(root.left), self.maxDepth(root.right)) \ No newline at end of file diff --git a/problems/python3/maximum-product-subarray.py b/problems/python3/maximum-product-subarray.py new file mode 100644 index 0000000..243bf41 --- /dev/null +++ b/problems/python3/maximum-product-subarray.py @@ -0,0 +1,25 @@ +""" +Time: O(N) +Space: O(N), can be reduce to O(1) + +dp[i][0] := The max product from subarray that end with nums[i] +dp[i][1] := The min product from subarray that end with nums[i] +""" +class Solution: + def maxProduct(self, nums: List[int]) -> int: + N = len(nums) + dp = [[1, 1] for _ in range(N+1)] + ans = float('-inf') + + for i in range(1, N+1): + dp[i][0] = dp[i][1] = nums[i-1] + + if nums[i-1]>0: + dp[i][0] = max(dp[i][0], nums[i-1]*dp[i-1][0]) + dp[i][1] = min(dp[i][1], nums[i-1]*dp[i-1][1]) + else: + dp[i][0] = max(dp[i][0], nums[i-1]*dp[i-1][1]) + dp[i][1] = min(dp[i][1], nums[i-1]*dp[i-1][0]) + ans = max(ans, dp[i][0]) + + return ans \ No newline at end of file diff --git a/problems/python3/maximum-subarray.py b/problems/python3/maximum-subarray.py new file mode 100644 index 0000000..6f749fa --- /dev/null +++ b/problems/python3/maximum-subarray.py @@ -0,0 +1,17 @@ +""" +Time: O(N) +Space: O(1) + +Calculate the prefix sum. Whenever the prefix is negative, we ignore all the value before. +Update the ans along the way. +""" +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + ans = float('-inf') + currSum = 0 + + for num in nums: + if currSum<0: currSum = 0 + currSum += num + ans = max(ans, currSum) + return ans \ No newline at end of file diff --git a/problems/python3/median-of-two-sorted-arrays.py b/problems/python3/median-of-two-sorted-arrays.py new file mode 100644 index 0000000..0fa31da --- /dev/null +++ b/problems/python3/median-of-two-sorted-arrays.py @@ -0,0 +1,40 @@ +""" +Try to find a i on array A and corespoding j on array B. + +Aleft = A[i] +Aright = A[i+1] +Bleft = B[j] +Bright = B[j+1] + +Such that Aleft<=Bright and Bleft<=Aright + +This means that A[:i+1] and B[:j+1] holds all the elements that is less than the median. +Aright A[i+1:] and B[j+1:] holds all the elements that is larger or equal to the median. +""" +class Solution: + def findMedianSortedArrays(self, A: List[int], B: List[int]) -> float: + if len(A)>len(B): A, B = B, A + + total = len(A)+len(B) + half = total//2 + l = 0 + r = len(A)-1 + + while True: + i = l + (r-l)//2 + j = half-(i+1)-1 + + Aleft = A[i] if i>=0 else float('-inf') + Aright = A[i+1] if i+1=0 else float('-inf') + Bright = B[j+1] if j+1Bright: + r = i-1 + else: + l = i+1 \ No newline at end of file diff --git a/problems/python3/meeting-rooms-ii.py b/problems/python3/meeting-rooms-ii.py new file mode 100644 index 0000000..1eb4eb4 --- /dev/null +++ b/problems/python3/meeting-rooms-ii.py @@ -0,0 +1,12 @@ +class Solution: + def minMeetingRooms(self, intervals: List[List[int]]) -> int: + intervals.sort() + h = [] + ans = 0 + + for s, e in intervals: + while h and s>=h[0][0]: + heapq.heappop(h) + heapq.heappush(h, (e, s)) + ans = max(ans, len(h)) + return ans \ No newline at end of file diff --git a/problems/python3/meeting-rooms.py b/problems/python3/meeting-rooms.py new file mode 100644 index 0000000..60cf429 --- /dev/null +++ b/problems/python3/meeting-rooms.py @@ -0,0 +1,10 @@ +class Solution: + def canAttendMeetings(self, intervals: List[List[int]]) -> bool: + intervals.sort() + lastEnd = float('-inf') + for s, e in intervals: + if lastEnd>s: + return False + else: + lastEnd = e + return True \ No newline at end of file diff --git a/problems/python3/merge-intervals.py b/problems/python3/merge-intervals.py new file mode 100644 index 0000000..c8a4678 --- /dev/null +++ b/problems/python3/merge-intervals.py @@ -0,0 +1,17 @@ +""" +Time: O(NLogN) for sorting +Space: O(1) excluding the output. +""" +class Solution: + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + ans = [] + intervals.sort() + + for s, e in intervals: + if not ans: + ans.append([s, e]) + elif ans[-1][1]>=s: + ans[-1][1] = max(ans[-1][1], e) + else: + ans.append([s, e]) + return ans \ No newline at end of file diff --git a/problems/python3/merge-triplets-to-form-target-triplet.py b/problems/python3/merge-triplets-to-form-target-triplet.py new file mode 100644 index 0000000..4708e4f --- /dev/null +++ b/problems/python3/merge-triplets-to-form-target-triplet.py @@ -0,0 +1,15 @@ +""" +If any element in the triplets is larger than the element in target, it cannot be used. +Check if we have all 3 index found the same value. +""" +class Solution: + def mergeTriplets(self, triplets: List[List[int]], target: List[int]) -> bool: + okIndex = set() + + for a, b, c in triplets: + if a>target[0] or b>target[1] or c>target[2]: continue + if a==target[0]: okIndex.add(0) + if b==target[1]: okIndex.add(1) + if c==target[2]: okIndex.add(2) + + return len(okIndex)==3 \ No newline at end of file diff --git a/problems/python3/merge-two-sorted-lists.py b/problems/python3/merge-two-sorted-lists.py new file mode 100644 index 0000000..2b31352 --- /dev/null +++ b/problems/python3/merge-two-sorted-lists.py @@ -0,0 +1,15 @@ +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + head = ListNode() #dummy + node = head + while list1 and list2: + if list1.val O(1). + +dp[i] := the cost to get to index i. +""" +class Solution: + def minCostClimbingStairs(self, cost: List[int]) -> int: + N = len(cost) + dp = [float('inf')]*(N+1) + dp[0] = 0 + dp[1] = 0 + + for i in range(2, len(dp)): + dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]) + return dp[-1] \ No newline at end of file diff --git a/problems/python3/min-cost-to-connect-all-points.py b/problems/python3/min-cost-to-connect-all-points.py new file mode 100644 index 0000000..4cd6ada --- /dev/null +++ b/problems/python3/min-cost-to-connect-all-points.py @@ -0,0 +1,28 @@ +class Solution: + def minCostConnectPoints(self, points: List[List[int]]) -> int: + ans = 0 + visited = set() + adj = collections.defaultdict(list) + N = len(points) + + #build adjacency list + for i in range(N): + x0, y0 = points[i] + for j in range(i+1, N): + x1, y1 = points[j] + dis = abs(x0-x1)+abs(y0-y1) + adj[(x0, y0)].append((dis, x1, y1)) + adj[(x1, y1)].append((dis, x0, y0)) + + h = [(0, points[0][0], points[0][1])] #min heap + while len(visited) None: + self.stack.append(val) + self.minStack.append(val if (not self.minStack or val None: + self.minStack.pop() + return self.stack.pop() + + def top(self) -> int: + return self.stack[-1] + + def getMin(self) -> int: + return self.minStack[-1] \ No newline at end of file diff --git a/problems/python3/minimum-interval-to-include-each-query.py b/problems/python3/minimum-interval-to-include-each-query.py new file mode 100644 index 0000000..ca43de3 --- /dev/null +++ b/problems/python3/minimum-interval-to-include-each-query.py @@ -0,0 +1,23 @@ +class Solution: + def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]: + ans = [-1]*len(queries) + queries = sorted([(query, i) for i, query in enumerate(queries)]) + intervals.sort() + h = [] + + itervalIndex = 0 + for query, queryIndex in queries: + #push all intervals that include 'query' to the min heap + while itervalIndex str: + if len(t)>len(s): return "" + + ans = "" + counter1 = collections.Counter(t) + charSet = set(t) + counter2 = collections.Counter() #sliding window in string s, index between l and r + charSet2 = set(s) + matchCount = 0 #count of char in the sliding window that counts are larger than the char count in t. + + l = 0 + for r in range(len(s)): + counter2[s[r]] += 1 + + if s[r] in charSet and counter1[s[r]]==counter2[s[r]]: matchCount += 1 + + while lcounter1[s[l]]): + counter2[s[l]] -= 1 + l += 1 + + if matchCount==len(charSet) and (ans=="" or r-l+1 int: + N = len(nums) + ans = 0 + + for n in range(N+1): + ans ^= n + + for n in nums: + ans ^= n + + return ans \ No newline at end of file diff --git a/problems/python3/multiply-strings.py b/problems/python3/multiply-strings.py new file mode 100644 index 0000000..be9f675 --- /dev/null +++ b/problems/python3/multiply-strings.py @@ -0,0 +1,22 @@ +class Solution: + def multiply(self, num1: str, num2: str) -> str: + if num1=='0' or num2=='0': return '0' + M, N = len(num1), len(num2) + temp = [0]*(M+N+1) + + num1, num2 = num1[::-1], num2[::-1] + for i in range(M): + for j in range(N): + digits = int(num1[i])*int(num2[j]) + temp[i+j] += digits + temp[i+j+1] += temp[i+j]//10 + temp[i+j] = temp[i+j]%10 + + ans = '' + temp = temp[::-1] + isLeadingZero = True + for d in temp: + if d!=0 or not isLeadingZero: + isLeadingZero = False + ans += str(d) + return ans \ No newline at end of file diff --git a/problems/python3/n-queens.py b/problems/python3/n-queens.py new file mode 100644 index 0000000..2ab7335 --- /dev/null +++ b/problems/python3/n-queens.py @@ -0,0 +1,45 @@ +class Solution: + def solveNQueens(self, n: int) -> List[List[str]]: + def helper(row: int): + if row==n: + ans.append(convertFormat(queenCols)) + return + + for col in range(n): + posDiag = row+col + negDiag = row-col + + if col in colUsed or posDiag in posDiagUsed or negDiag in negDiagUsed: continue + + queenCols.append(col) + colUsed.add(col) + posDiagUsed.add(posDiag) + negDiagUsed.add(negDiag) + + helper(row+1) + + queenCols.pop() + colUsed.remove(col) + posDiagUsed.remove(posDiag) + negDiagUsed.remove(negDiag) + + def convertFormat(queenCols: List[int]) -> List[str]: + output = [] + for col in queenCols: + row = '' + for i in range(n): + if i==col: + row += 'Q' + else: + row += '.' + output.append(row) + return output + + ans = [] + queenCols = [] + colUsed = set() + posDiagUsed = set() + negDiagUsed = set() + + helper(0) + return ans \ No newline at end of file diff --git a/problems/python3/network-delay-time.py b/problems/python3/network-delay-time.py new file mode 100644 index 0000000..6da8613 --- /dev/null +++ b/problems/python3/network-delay-time.py @@ -0,0 +1,23 @@ +class Solution: + def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int: + ans = 0 + adj = collections.defaultdict(list) + h = [] + visited = set() + + for u, v, w in times: + adj[u].append((v, w)) + + heapq.heappush(h, (0, k)) + while h: + timeNeededToGetHere, node = heapq.heappop(h) + + if node in visited: continue + visited.add(node) + ans = max(ans, timeNeededToGetHere) + + for nei, time in adj[node]: + if nei in visited: continue + heapq.heappush(h, (time+timeNeededToGetHere, nei)) + + return ans if len(visited)==n else -1 \ No newline at end of file diff --git a/problems/python3/non-overlapping-intervals.py b/problems/python3/non-overlapping-intervals.py new file mode 100644 index 0000000..b3b6a0d --- /dev/null +++ b/problems/python3/non-overlapping-intervals.py @@ -0,0 +1,17 @@ +""" +Time: O(NLogN) for sorting. +""" +class Solution: + def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: + intervals.sort() + + ans = 0 + prevEnd = intervals[0][1] + + for s, e in intervals[1:]: + if s>=prevEnd: + prevEnd = e + else: + ans += 1 + prevEnd = min(prevEnd, e) + return ans \ No newline at end of file diff --git a/problems/python3/number-of-1-bits.py b/problems/python3/number-of-1-bits.py new file mode 100644 index 0000000..2273859 --- /dev/null +++ b/problems/python3/number-of-1-bits.py @@ -0,0 +1,10 @@ +""" +n = n&(n-1) will turn the right most 1 to 0. +""" +class Solution: + def hammingWeight(self, n: int) -> int: + ans = 0 + while n>0: + n = n&(n-1) + ans += 1 + return ans \ No newline at end of file diff --git a/problems/python3/number-of-connected-components-in-an-undirected-graph.py b/problems/python3/number-of-connected-components-in-an-undirected-graph.py new file mode 100644 index 0000000..28571ea --- /dev/null +++ b/problems/python3/number-of-connected-components-in-an-undirected-graph.py @@ -0,0 +1,25 @@ +class Solution: + def countComponents(self, N: int, edges: List[List[int]]) -> int: + def union(n1, n2): + p1 = find(n1) + p2 = find(n2) + if p1==p2: + return + elif p1 int: + def dfs(i, j): + if i<0 or j<0 or i>=MAX_ROWS or j>=MAX_COLS: return + if grid[i][j]!='1': return + + grid[i][j] = '2' + dfs(i+1, j) + dfs(i-1, j) + dfs(i, j+1) + dfs(i, j-1) + + count = 0 + MAX_ROWS = len(grid) + MAX_COLS = len(grid[0]) + + for i in range(MAX_ROWS): + for j in range(MAX_COLS): + if grid[i][j]=='1': + dfs(i, j) + count += 1 + return count \ No newline at end of file diff --git a/problems/python3/pacific-atlantic-water-flow.py b/problems/python3/pacific-atlantic-water-flow.py new file mode 100644 index 0000000..05032b0 --- /dev/null +++ b/problems/python3/pacific-atlantic-water-flow.py @@ -0,0 +1,30 @@ +class Solution: + def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]: + def bfs(q, ocian): + while q: + i0, j0 = q.popleft() + if (i0, j0) in ocian: continue + ocian.add((i0, j0)) + + for i, j in ((i0+1, j0), (i0-1, j0), (i0, j0+1), (i0, j0-1)): + if i<0 or j<0 or i>=MAX_ROW or j>=MAX_COL: continue + if heights[i][j]>=heights[i0][j0]: q.append((i, j)) + + MAX_ROW = len(heights) + MAX_COL = len(heights[0]) + + #add the top and left to q1 + pacific = set() + q1 = collections.deque() + for j in range(MAX_COL): q1.append((0, j)) + for i in range(MAX_ROW): q1.append((i, 0)) + + #add botton and right to q2 + atlantic = set() + q2 = collections.deque() + for j in range(MAX_COL): q2.append((MAX_ROW-1, j)) + for i in range(MAX_ROW): q2.append((i, MAX_COL-1)) + + bfs(q1, pacific) + bfs(q2, atlantic) + return pacific.intersection(atlantic) \ No newline at end of file diff --git a/problems/python3/palindrome-partitioning.py b/problems/python3/palindrome-partitioning.py new file mode 100644 index 0000000..d02ef17 --- /dev/null +++ b/problems/python3/palindrome-partitioning.py @@ -0,0 +1,64 @@ +""" +`helper(i)` finds all the palindrome substring from i to j see if we can get an answer by recursively calling `helper(j+1)`. + +Time: O(2^N * N), for a string length S, there will be 2^N combination of substrings. For each substring, it will take O(N) to test if they are all palindrome. +Space: O(N). `partition` will takes O(N) and recursion stack will also take O(N). O(N + N) ~= O(N) +""" +class Solution: + def partition(self, s: str) -> List[List[str]]: + def helper(i): + if i>=len(s): + ans.append(partition.copy()) + return + + for j in range(i, len(s)): + if isPalindrome(i, j): + partition.append(s[i:j+1]) + helper(j+1) + partition.pop() + + def isPalindrome(i, j): + while i<=j: + if s[i]!=s[j]: return False + i += 1 + j -= 1 + return True + + ans = [] + partition = [] + helper(0) + return ans + +""" +If you look closely you can see that we execute `isPalindrome` on many repeated substrings. +We can optimize this by storing the result in `dp` and reuse it. +Since all the less difference i and j (shorter substring s[i:j+1]) will be process first in the `isPalindrome`. +When checking if s[i:j+1] is a palindrome or not, we can simply check the if s[i]==s[j] and the previous result (dp[i+1][j-1]) + +Time: O(2^N * N), The overall time complexity is the same, but isPalindrome is actually a lot faster. +Space: O(N^2) for `dp`. +""" +class Solution: + def partition(self, s: str) -> List[List[str]]: + def helper(i): + if i>=N: + ans.append(partition.copy()) + return + + for j in range(i, N): + if isPalindrome(i, j): + partition.append(s[i:j+1]) + helper(j+1) + partition.pop() + + def isPalindrome(i, j): + dp[i][j] = i==j or (j-i==1 and s[i]==s[j]) or (s[i]==s[j] and dp[i+1][j-1]) #len==1 palindrome or len==2 palindrome or len>=3 palindrome + return dp[i][j] + + N = len(s) + ans = [] + partition = [] + dp = [[False]*N for _ in range(N)] + + helper(0) + return ans \ No newline at end of file diff --git a/problems/python3/palindromic-substrings.py b/problems/python3/palindromic-substrings.py new file mode 100644 index 0000000..7ef7e96 --- /dev/null +++ b/problems/python3/palindromic-substrings.py @@ -0,0 +1,16 @@ +class Solution: + def countSubstrings(self, s: str) -> int: + def countPalindrome(l, r) -> int: + count = 0 + while l>=0 and r bool: + total = sum(nums) + if total%2!=0: return False + + target = total/2 + possibleSum = set() + possibleSum.add(0) + for num in nums: + temp = set() + for p in possibleSum: + if p==target or p+num==target: return True + temp.add(p) + temp.add(p+num) + possibleSum = temp + return False \ No newline at end of file diff --git a/problems/python3/partition-labels.py b/problems/python3/partition-labels.py new file mode 100644 index 0000000..0c12d00 --- /dev/null +++ b/problems/python3/partition-labels.py @@ -0,0 +1,26 @@ +""" +Time: O(N) +Space: O(N) + +For each char, store its max index in maxIndex. +Iterate through the string, update the "currMax" along the way. +currMax is the place we can partition unless it get updated again. +If the current index is the currMax, update "ans". +""" +class Solution: + def partitionLabels(self, s: str) -> List[int]: + ans = [] + maxIndex = {} + + for i, c in enumerate(s): + maxIndex[c] = i + + currMax = 0 + processedLength = 0 + for i, c in enumerate(s): + currMax = max(currMax, maxIndex[c]) + if i==currMax: + ans.append(i+1 - processedLength) + processedLength += ans[-1] + + return ans \ No newline at end of file diff --git a/problems/python3/permutation-in-string.py b/problems/python3/permutation-in-string.py new file mode 100644 index 0000000..cd22781 --- /dev/null +++ b/problems/python3/permutation-in-string.py @@ -0,0 +1,28 @@ +class Solution: + def checkInclusion(self, s1: str, s2: str) -> bool: + if len(s1)>len(s2): return False + + counter1 = collections.Counter(s1) + counter2 = collections.Counter(s2[:len(s1)]) + matches = 0 + for c in 'abcdefghijklmnopqrstuvwxyz': + if counter1[c]==counter2[c]: matches += 1 + if matches==26: return True + + l = 0 + for r in range(len(s1), len(s2)): + counter2[s2[r]] += 1 + if counter1[s2[r]]==counter2[s2[r]]: + matches += 1 + elif counter1[s2[r]]+1==counter2[s2[r]]: + matches -= 1 + + counter2[s2[l]] -= 1 + if counter1[s2[l]]==counter2[s2[l]]: + matches += 1 + elif counter1[s2[l]]-1==counter2[s2[l]]: + matches -= 1 + l += 1 + + if matches==26: return True + return False \ No newline at end of file diff --git a/problems/python3/permutations.py b/problems/python3/permutations.py new file mode 100644 index 0000000..98d536d --- /dev/null +++ b/problems/python3/permutations.py @@ -0,0 +1,28 @@ +""" +Time: O(N!), since we call helper() N! times. +Space: O(N) for recursion stacks. + +Whenever we call `helper()`, we pick a num that is not "used" and add it to the `permutation`. +Recursively call the `helper()` until we filled the `permutation`. +Resotre `permutation` and `used` and try another `num`. +""" +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + def helper(): + if len(permutation)==len(nums): + ans.append(permutation.copy()) + return + + for num in nums: + if num in used: continue + used.add(num) + permutation.append(num) + helper() + used.remove(num) + permutation.pop() + + ans = [] + permutation = [] + used = set() + helper() + return ans \ No newline at end of file diff --git a/problems/python3/plus-one.py b/problems/python3/plus-one.py new file mode 100644 index 0000000..8cfedb4 --- /dev/null +++ b/problems/python3/plus-one.py @@ -0,0 +1,16 @@ +class Solution: + def plusOne(self, digits: List[int]) -> List[int]: + i = len(digits)-1 + needAdditionDigit = True + + while i>=0 and needAdditionDigit: + if digits[i]==9: + digits[i] = 0 + i -= 1 + needAdditionDigit = True + else: + digits[i] += 1 + needAdditionDigit = False + if needAdditionDigit: digits.insert(0, 1) + return digits + \ No newline at end of file diff --git a/problems/python3/powx-n.py b/problems/python3/powx-n.py new file mode 100644 index 0000000..2b1b4fc --- /dev/null +++ b/problems/python3/powx-n.py @@ -0,0 +1,14 @@ +class Solution: + def myPow(self, x: float, k: int) -> float: + if k<0: return 1/self.myPow(x, -k) + + if k==0: + return 1 + elif k==1: + return x + elif k%2==0: + half = self.myPow(x, k//2) + return half * half + else: + half = self.myPow(x, (k-1)//2) + return half * half * x \ No newline at end of file diff --git a/problems/python3/product-of-array-except-self.py b/problems/python3/product-of-array-except-self.py new file mode 100644 index 0000000..2309896 --- /dev/null +++ b/problems/python3/product-of-array-except-self.py @@ -0,0 +1,45 @@ +class Solution: + def productExceptSelf(self, nums: List[int]) -> List[int]: + #left[i] := product of all nums left of nums[i] (not include nums[i]) + left = [1] + temp = 1 + for num in nums: + temp *= num + left.append(temp) + + #right[i] := product of all nums right of nums[i] (not include nums[i]) + right = [] + temp = 1 + for num in reversed(nums): + temp *= num + right.append(temp) + right.reverse() + right.append(1) + right = right[1:] + + ans = [] + for i in range(len(nums)): + ans.append(left[i]*right[i]) + return ans + +#In Place +class Solution: + def productExceptSelf(self, nums: List[int]) -> List[int]: + N = len(nums) + ans = [0]*N + + #generate "left" + ans[0] = 1 + for i in range(1, N): + ans[i] = ans[i-1]*nums[i-1] + + #generate "right" + temp = 1 + for i in range(N-2, -1, -1): + temp *= nums[i+1] + ans[i] *= temp + + return ans + + + \ No newline at end of file diff --git a/problems/python3/reconstruct-itinerary.py b/problems/python3/reconstruct-itinerary.py new file mode 100644 index 0000000..14c928f --- /dev/null +++ b/problems/python3/reconstruct-itinerary.py @@ -0,0 +1,27 @@ +""" +DFS with backtracking. +""" +class Solution: + def findItinerary(self, tickets: List[List[str]]) -> List[str]: + def dfs(start) -> bool: + if len(ans)==len(tickets)+1: return True + if start not in adj: return False + + temp = list(adj[start]) + for i, arr in enumerate(temp): + adj[start].pop(i) + ans.append(arr) + if dfs(arr): return True + adj[start].insert(i, arr) + ans.pop() + return False + + ans = ['JFK'] + adj = collections.defaultdict(list) + + tickets.sort() + for des, arr in tickets: + adj[des].append(arr) + + dfs('JFK') + return ans \ No newline at end of file diff --git a/problems/python3/redundant-connection.py b/problems/python3/redundant-connection.py new file mode 100644 index 0000000..47bda2e --- /dev/null +++ b/problems/python3/redundant-connection.py @@ -0,0 +1,32 @@ +class Solution: + def findRedundantConnection(self, edges: List[List[int]]) -> List[int]: + def union(n1, n2): + p1 = find(n1) + p2 = find(n2) + + if p1==p2: + return False #union failed, already united. + elif p1 bool: + def dfs(i, j): + if (i, j) in cache: return cache[(i, j)] + if i>=M and j>=N: return True + if j>=N: return False + + match = i Optional[ListNode]: + #count the length of the linked list + node = head + count = 0 + while node: + count += 1 + node = node.next + + + node = head + steps = count-n-1 + + #steps==-1 means that we need to remove the first node + if steps==-1: return head.next + + #traverse to the node before the node we wanted to remove + while steps>0: + node = node.next + steps -= 1 + + #remove "node.next" + node.next = node.next.next + + return head + + +#One pass. Fast pointer is ahead of slow pointer by n+1 +#So slow pointer will stop at the node before the node we wanted to remove +class Solution: + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + dummy = ListNode() + dummy.next = head + + ahead = n+1 + fast = dummy + slow = dummy + + while fast: + fast = fast.next + ahead -= 1 + if ahead<0: slow = slow.next + + slow.next = slow.next.next + + return dummy.next \ No newline at end of file diff --git a/problems/python3/reorder-list.py b/problems/python3/reorder-list.py new file mode 100644 index 0000000..89c6ba2 --- /dev/null +++ b/problems/python3/reorder-list.py @@ -0,0 +1,37 @@ +class Solution: + def reorderList(self, head: Optional[ListNode]) -> None: + #find the middle point + slow = head + fast = head + while fast and fast.next: + slow = slow.next + fast = fast.next.next + middle = slow.next + + #reverse the linked list after the middle point + middle = self.reverseList(middle) + + #separate the linked list before the middle + slow.next = None + + #merge two linked list + node = head + while middle and node: + nextNode = node.next + node.next = middle + middle = middle.next + node.next.next = nextNode + node = nextNode + return head + + def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + pre = None + node = head + + while node: + nextNode = node.next + node.next = pre + if not nextNode: return node + pre = node + node = nextNode + \ No newline at end of file diff --git a/problems/python3/reverse-bits.py b/problems/python3/reverse-bits.py new file mode 100644 index 0000000..dfc5191 --- /dev/null +++ b/problems/python3/reverse-bits.py @@ -0,0 +1,7 @@ +class Solution: + def reverseBits(self, n: int) -> int: + res = 0 + for i in range(32): + bit = (n >> i) & 1 + res = res | (bit << (31 - i)) + return res \ No newline at end of file diff --git a/problems/python3/reverse-integer.py b/problems/python3/reverse-integer.py new file mode 100644 index 0000000..76137ab --- /dev/null +++ b/problems/python3/reverse-integer.py @@ -0,0 +1,16 @@ +class Solution: + def reverse(self, x: int) -> int: + MAX = 2**31-1 + MIN = -2**31 + ans = 0 + + while x: + digit = int(math.fmod(x, 10)) + x = int(x/10) + + if ans>MAX//10 or (ans==MAX//10 and digit>MAX%10): return 0 + if ans Optional[ListNode]: + if not head or not head.next: return head + temp = self.reverseList(head.next) + head.next.next = head + head.next = None + return temp + +#Iterative +class Solution: + def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + pre = None + node = head + + while node: + nextNode = node.next + node.next = pre + if not nextNode: return node + pre = node + node = nextNode \ No newline at end of file diff --git a/problems/python3/rotate-image.py b/problems/python3/rotate-image.py new file mode 100644 index 0000000..c8a872a --- /dev/null +++ b/problems/python3/rotate-image.py @@ -0,0 +1,16 @@ +class Solution: + def rotate(self, matrix: List[List[int]]) -> None: + l, r = 0, len(matrix[0])-1 + + while l int: + time = 0 + rotten = set() + aboutToRot = set() + fresh = set() + + for i in range(len(grid)): + for j in range((len(grid[0]))): + if grid[i][j]==2: + rotten.add((i, j)) + elif grid[i][j]==1: + fresh.add((i, j)) + + while rotten: + i0, j0 = rotten.pop() #randomly get one + grid[i0][j0] = 2 + + for i, j in ((i0+1, j0), (i0-1, j0), (i0, j0+1), (i0, j0-1)): + if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]): continue + if (i, j) in rotten or (i, j) in aboutToRot: continue + if (i, j) in fresh: + fresh.remove((i, j)) + aboutToRot.add((i, j)) + + if not rotten and not aboutToRot: break + + if not rotten: + time += 1 + rotten = aboutToRot + aboutToRot = set() + + if fresh: return -1 + + return time \ No newline at end of file diff --git a/problems/python3/same-tree.py b/problems/python3/same-tree.py new file mode 100755 index 0000000..6687f04 --- /dev/null +++ b/problems/python3/same-tree.py @@ -0,0 +1,16 @@ +""" +Time: O(N) +Space: O(LogN) if the tree is balanced. +""" +class Solution: + def isSameTree(self, node1: Optional[TreeNode], node2: Optional[TreeNode]) -> bool: + if not node1 and not node2: + return True + + if (not node1 and node2) or (node1 and not node2): + return False + + if node1.val!=node2.val: + return False + + return self.isSameTree(node1.left, node2.left) and self.isSameTree(node1.right, node2.right) \ No newline at end of file diff --git a/problems/python3/search-a-2d-matrix.py b/problems/python3/search-a-2d-matrix.py new file mode 100644 index 0000000..2afe301 --- /dev/null +++ b/problems/python3/search-a-2d-matrix.py @@ -0,0 +1,22 @@ +class Solution: + def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: + N = len(matrix) + M = len(matrix[0]) + + l = 0 + r = N*M-1 + + while l<=r: + m = l + int((r-l)/2) + + i = int(m/M) + j = m%M + + if matrix[i][j]target: + r = m-1 + else: + return True + + return False \ No newline at end of file diff --git a/problems/python3/serialize-and-deserialize-binary-tree.py b/problems/python3/serialize-and-deserialize-binary-tree.py new file mode 100644 index 0000000..1b057d0 --- /dev/null +++ b/problems/python3/serialize-and-deserialize-binary-tree.py @@ -0,0 +1,22 @@ +class Codec: + + def serialize(self, root): + if not root: return '#' + return str(root.val)+','+self.serialize(root.left)+','+self.serialize(root.right) + + + def deserialize(self, data): + def helper(): + if data[self.i]=='#': + self.i += 1 + return None + + node = TreeNode(int(data[self.i])) + self.i += 1 + node.left = helper() + node.right = helper() + return node + + data = data.split(",") + self.i = 0 + return helper() \ No newline at end of file diff --git a/problems/python3/set-matrix-zeroes.py b/problems/python3/set-matrix-zeroes.py new file mode 100644 index 0000000..dd836e5 --- /dev/null +++ b/problems/python3/set-matrix-zeroes.py @@ -0,0 +1,28 @@ +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + M = len(matrix) + N = len(matrix[0]) + firstRowZero = False + + for i in range(M): + for j in range(N): + if matrix[i][j]==0: + matrix[0][j] = 0 + if i==0: + firstRowZero = True + else: + matrix[i][0] = 0 + + for i in range(1, M): + if matrix[i][0]==0: + for j in range(N): + matrix[i][j] = 0 + + for j in range(N): + if matrix[0][j]==0: + for i in range(M): + matrix[i][j] = 0 + + if firstRowZero: + for j in range(N): + matrix[0][j] = 0 \ No newline at end of file diff --git a/problems/python3/single-number.py b/problems/python3/single-number.py new file mode 100644 index 0000000..daa3268 --- /dev/null +++ b/problems/python3/single-number.py @@ -0,0 +1,15 @@ +""" +^ (XOR) +The same will be 0 +0^0 = 0 +1^1 = 0 + +Different will be 1 +1^0 = 1 +0^1 = 1 +""" +class Solution: + def singleNumber(self, nums: List[int]) -> int: + ans = 0 + for num in nums: ans ^= num + return ans \ No newline at end of file diff --git a/problems/python3/sliding-window-maximum.py b/problems/python3/sliding-window-maximum.py new file mode 100644 index 0000000..a535c16 --- /dev/null +++ b/problems/python3/sliding-window-maximum.py @@ -0,0 +1,18 @@ +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: + ans = [] + q = collections.deque() + l = r = 0 + + while r List[int]: + ans = [] + x0 = 0 + y0 = 0 + dx = len(matrix[0])-1 + dy = len(matrix)-1 + direction = 'right' + isFirst = True + + ans.append(matrix[x0][y0]) + while True: + if direction=='right': + for x in range(x0+1, x0+dx+1): + ans.append(matrix[y0][x]) + x0 += dx + direction = 'down' + + if isFirst: + isFirst = False + else: + dx -= 1 + + if dy==0: break + + elif direction=='left': + for x in range(x0-1, x0-dx-1, -1): + ans.append(matrix[y0][x]) + x0 -= dx + direction = 'up' + dx -= 1 + if dy==0: break + + elif direction=='down': + for y in range(y0+1, y0+dy+1): + ans.append(matrix[y][x0]) + y0 += dy + direction = 'left' + dy -= 1 + if dx==0: break + + elif direction=='up': + for y in range(y0-1, y0-dy-1, -1): + ans.append(matrix[y][x0]) + y0 -= dy + direction = 'right' + dy -= 1 + if dx==0: break + return ans + + +""" +Answer from Neetcode, more elegant. +left, right, top, bottom is the border (index is exclusive on the border. +In other words, for matrix[i][j] +i: top List[int]: + res = [] + left, right = 0, len(matrix[0]) + top, bottom = 0, len(matrix) + + while left < right and top < bottom: + # get every i in the top row + for i in range(left, right): + res.append(matrix[top][i]) + top += 1 + # get every i in the right col + for i in range(top, bottom): + res.append(matrix[i][right - 1]) + right -= 1 + if not (left < right and top < bottom): + break + # get every i in the bottom row + for i in range(right - 1, left - 1, -1): + res.append(matrix[bottom - 1][i]) + bottom -= 1 + # get every i in the left col + for i in range(bottom - 1, top - 1, -1): + res.append(matrix[i][left]) + left += 1 + + return res \ No newline at end of file diff --git a/problems/python3/subsets-ii.py b/problems/python3/subsets-ii.py new file mode 100644 index 0000000..be0b0b7 --- /dev/null +++ b/problems/python3/subsets-ii.py @@ -0,0 +1,23 @@ +""" +Time: O(N * 2^N) +Space: O(N) +""" +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + def helper(i): + if i==len(nums): + ans.append(subset.copy()) + return + + subset.append(nums[i]) + helper(i+1) + subset.pop() + + while i+1 List[List[int]]: + def helper(i): + if not i List[int]: + N = len(s) + M = len(words) + W = len(words[0]) + wordSet = set(words) + ans = [] + counter = collections.Counter(words) + + for i in range(W): #[0] + windowCounter = collections.Counter() #counter for the word in words + notInWords = 0 #number of word not in the wordSet + theSame = 0 #number of word with the same count with "counter" + j = i + + while jcounter[word]: + theSame -= 1 + else: + notInWords += 1 + + popStart = j-M*W + if popStart>=0: + popWord = s[popStart:popStart+W] + if popWord in wordSet: + windowCounter[popWord] -= 1 + if windowCounter[popWord]==counter[popWord]: + theSame += 1 + elif windowCounter[popWord] bool: + if not root or not subRoot: return root==subRoot + if self.isSame(root, subRoot): return True + return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot) + + def isSame(self, p, q): + if not p or not q: return p==q + if p.val!=q.val: return False + return self.isSame(p.left, q.left) and self.isSame(p.right, q.right) \ No newline at end of file diff --git a/problems/python3/sum-of-two-integers.py b/problems/python3/sum-of-two-integers.py new file mode 100644 index 0000000..25f16f9 --- /dev/null +++ b/problems/python3/sum-of-two-integers.py @@ -0,0 +1,12 @@ +""" +Does not work with negative values yet. +""" +class Solution: + def getSum(self, a: int, b: int) -> int: + ans = a^b + carry = (a&b)<<1 + + while carry!=0: + ans, carry = ans^carry, (ans&carry)<<1 + + return ans \ No newline at end of file diff --git a/problems/python3/surrounded-regions.py b/problems/python3/surrounded-regions.py new file mode 100644 index 0000000..42aa4c0 --- /dev/null +++ b/problems/python3/surrounded-regions.py @@ -0,0 +1,34 @@ +""" +Time: O(N) +Space: O(N) +""" +class Solution: + def solve(self, board: List[List[str]]) -> None: + def dfs(i0, j0): + if i0<0 or j0<0 or i0>=MAX_ROW or j0>=MAX_COL: return + if board[i0][j0]!='O': return + if (i0, j0) in survived: return + + survived.add((i0, j0)) + dfs(i0+1, j0) + dfs(i0-1, j0) + dfs(i0, j0+1) + dfs(i0, j0-1) + + + MAX_ROW = len(board) + MAX_COL = len(board[0]) + survived = set() + + for i in range(MAX_ROW): + dfs(i, 0) + dfs(i, MAX_COL-1) + + for j in range(MAX_COL): + dfs(0, j) + dfs(MAX_ROW-1, j) + + for i in range(MAX_ROW): + for j in range(MAX_COL): + board[i][j] = 'O' if (i, j) in survived else 'X' + return board \ No newline at end of file diff --git a/problems/python3/swim-in-rising-water.py b/problems/python3/swim-in-rising-water.py new file mode 100644 index 0000000..dd5dbef --- /dev/null +++ b/problems/python3/swim-in-rising-water.py @@ -0,0 +1,23 @@ +""" +Time: O(N^2 * LogN^2) = O(N^2 * 2LogN) = O(N^2LogN), N is the number of elements in a row or column. +Space: O(N^2) +""" +class Solution: + def swimInWater(self, grid: List[List[int]]) -> int: + ROWS = len(grid) + COLS = len(grid[0]) + + visited = set() + h = [(grid[0][0], 0, 0)] + + while h: + t, r0, c0 = heapq.heappop(h) + + if (r0, c0) in visited: continue + visited.add((r0, c0)) + if r0==ROWS-1 and c0==COLS-1: return t + + for r, c in ((r0+1, c0), (r0-1, c0), (r0, c0+1), (r0, c0-1)): + if r<0 or c<0 or r>=ROWS or c>=COLS: continue + if (r, c) in visited: continue + heapq.heappush(h, (max(t, grid[r][c]), r, c)) \ No newline at end of file diff --git a/problems/python3/target-sum.py b/problems/python3/target-sum.py new file mode 100644 index 0000000..832fadd --- /dev/null +++ b/problems/python3/target-sum.py @@ -0,0 +1,25 @@ +""" +Time: O(NS), S is sum(nums), N is len(nums). This is the max possible number of element in "history". Which will be lesser than 2^N. +Space: O(NS) +""" +class Solution: + def findTargetSumWays(self, nums: List[int], target: int) -> int: + def dfs(i, curr): + #cache + if (i, curr) in history: + return history[(i, curr)] + + #ending condition + if i==len(nums): + if curr==target: + history[(i, curr)] = 1 + else: + history[(i, curr)] = 0 + return history[(i, curr)] + + history[(i, curr)] = dfs(i+1, curr+nums[i])+dfs(i+1, curr-nums[i]) + return history[(i, curr)] + + ans = 0 + history = {} + return dfs(0, 0) \ No newline at end of file diff --git a/problems/python3/task-scheduler.py b/problems/python3/task-scheduler.py new file mode 100644 index 0000000..ddc4625 --- /dev/null +++ b/problems/python3/task-scheduler.py @@ -0,0 +1,23 @@ +class Solution: + def leastInterval(self, tasks: List[str], n: int) -> int: + q = collections.deque() + h = [] + time = 0 + + counter = collections.Counter(tasks) + for task in counter: + heapq.heappush(h, (-counter[task], task)) + + while h or q: + if q and q[0][0]<=time: + _, count, task = q.popleft() + heapq.heappush(h, (-count, task)) + + if h: + count, task = heapq.heappop(h) + count*=-1 + count -= 1 + if count>0: q.append((time+n+1, count, task)) + time += 1 + + return time \ No newline at end of file diff --git a/problems/python3/time-based-key-value-store.py b/problems/python3/time-based-key-value-store.py new file mode 100644 index 0000000..f43facf --- /dev/null +++ b/problems/python3/time-based-key-value-store.py @@ -0,0 +1,28 @@ +class TimeMap: + + def __init__(self): + self.data = collections.defaultdict(list) + + def set(self, key: str, value: str, timestamp: int) -> None: + self.data[key].append((timestamp, value)) + + def get(self, key: str, timestamp: int) -> str: + dataList = self.data[key] + l = 0 + r = len(dataList)-1 + ans = '' + + while l<=r: + m = l + int((r-l)/2) + t = dataList[m][0] + + if ttimestamp: + r = m-1 + else: + ans = dataList[m][1] + break + + return ans \ No newline at end of file diff --git a/problems/python3/top-k-frequent-elements.py b/problems/python3/top-k-frequent-elements.py new file mode 100644 index 0000000..7d728cb --- /dev/null +++ b/problems/python3/top-k-frequent-elements.py @@ -0,0 +1,53 @@ +#Bucket Sort +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + bucket = collections.defaultdict(list) + counter = collections.Counter(nums) + ans = [] + + for num in counter: + bucket[counter[num]].append(num) + + tempCount = len(nums) + while len(ans) List[int]: + def quickSelect(freqs, s, e, K): + i = s + t = s + j = e + pivot = freqs[(s+e)//2][1] + + while t<=j: + if freqs[t][1]pivot: + freqs[j], freqs[t] = freqs[t], freqs[j] + j -= 1 + else: + t += 1 + if e-j>=K: + return quickSelect(freqs, j+1, e, K) + elif e-(i-1)>=K: + return pivot + else: + return quickSelect(freqs, s, i-1, K-(e-i+1)) + + counts = collections.Counter(nums) + freqs = [(num, counts[num]) for num in counts] + ans = [] + + KthLargestFreq = quickSelect(freqs, 0, len(freqs)-1, K) + + for num, freq in freqs: + if freq>=KthLargestFreq: + ans.append(num) + return ans \ No newline at end of file diff --git a/problems/python3/trapping-rain-water.py b/problems/python3/trapping-rain-water.py new file mode 100644 index 0000000..fe01411 --- /dev/null +++ b/problems/python3/trapping-rain-water.py @@ -0,0 +1,35 @@ +""" +The water stored at i is `min(leftMax, rightMax) - height[i]` +leftMax: max height left of i +rightMax: max height right of i + +To use only constant space. +We can calculete the leftMax or rightMax and update ans along the way. +And we always go with the side where leftMax or rightMax is smaller. + +Time: O(N) +Space: O(1) +""" +class Solution: + def trap(self, height: List[int]) -> int: + ans = 0 + l = 0 + r = len(height)-1 + + leftMax = float('-inf') + rightMax = float('-inf') + + while l<=r: + if leftMax=leftMax: + leftMax = height[l] + else: + ans += leftMax-height[l] + l += 1 + else: + if height[r]>=rightMax: + rightMax = height[r] + else: + ans += rightMax-height[r] + r -= 1 + return ans \ No newline at end of file diff --git a/problems/python3/two-sum-ii-input-array-is-sorted.py b/problems/python3/two-sum-ii-input-array-is-sorted.py new file mode 100644 index 0000000..2890404 --- /dev/null +++ b/problems/python3/two-sum-ii-input-array-is-sorted.py @@ -0,0 +1,11 @@ +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + i = 0 + j = len(numbers)-1 + + while numbers[i]+numbers[j] != target: + if numbers[i]+numbers[j] > target: + j -= 1 + else: + i += 1 + return (i+1, j+1) \ No newline at end of file diff --git a/problems/python3/two-sum.py b/problems/python3/two-sum.py new file mode 100644 index 0000000..aa90d66 --- /dev/null +++ b/problems/python3/two-sum.py @@ -0,0 +1,8 @@ +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + #needed: {number required : counter part index} + needed = {} + for i, num in enumerate(nums): + if num in needed: + return (needed[num], i) + needed[target-num] = i \ No newline at end of file diff --git a/problems/python3/unique-paths.py b/problems/python3/unique-paths.py new file mode 100644 index 0000000..9c7ce40 --- /dev/null +++ b/problems/python3/unique-paths.py @@ -0,0 +1,11 @@ +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + m = m-1 #number of steps need to move down + n = n-1 #number of steps need to move right + + #the total combination of m and n to sort will be (m+n)! + #since all "move down" are consider the same, we need to remove the repeatition of it sorting: m!. + #since all "move right" are consider the same, we need to remove the repeatition of it sorting: n!. + #(m+n)!/m!n! + + return math.factorial(m+n)//(math.factorial(m)*math.factorial(n)) \ No newline at end of file diff --git a/problems/python3/valid-anagram.py b/problems/python3/valid-anagram.py new file mode 100644 index 0000000..6c37cb0 --- /dev/null +++ b/problems/python3/valid-anagram.py @@ -0,0 +1,17 @@ +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + counter = collections.Counter() + + for c in s: + counter[c] += 1 + + for c in t: + if c not in counter: + return False + counter[c] -= 1 + + for c in counter: + if counter[c]>0: + return False + + return True \ No newline at end of file diff --git a/problems/python3/valid-palindrome.py b/problems/python3/valid-palindrome.py new file mode 100644 index 0000000..0069c83 --- /dev/null +++ b/problems/python3/valid-palindrome.py @@ -0,0 +1,15 @@ +class Solution: + def isPalindrome(self, s: str) -> bool: + i = 0 + j = len(s)-1 + + while i<=j: + while i bool: + mapping = {')': '(', ']': '[', '}':'{'} + stack = [] + + for c in s: + if c not in mapping: + #open parentheses + stack.append(c) + else: + #close parentheses + if stack and stack[-1]==mapping[c]: + stack.pop() + else: + return False + + return not stack \ No newline at end of file diff --git a/problems/python3/valid-parenthesis-string.py b/problems/python3/valid-parenthesis-string.py new file mode 100644 index 0000000..52ea446 --- /dev/null +++ b/problems/python3/valid-parenthesis-string.py @@ -0,0 +1,23 @@ +class Solution: + def checkValidString(self, s: str) -> bool: + leftMin = 0 + leftMax = 0 + + for c in s: + if c=='(': + leftMin += 1 + leftMax += 1 + elif c==')': + leftMin -= 1 + leftMax -= 1 + else: + leftMin -= 1 + leftMax += 1 + + if leftMax<0: + return False + + if leftMin<0: + leftMin = 0 + + return leftMin == 0 \ No newline at end of file diff --git a/problems/python3/valid-sudoku.py b/problems/python3/valid-sudoku.py new file mode 100644 index 0000000..b2e6fa7 --- /dev/null +++ b/problems/python3/valid-sudoku.py @@ -0,0 +1,37 @@ +class Solution: + def isValidSudoku(self, board: List[List[str]]) -> bool: + def rowIsValid(i): + used = set() + for j in range(N): + if board[i][j]=='.': continue + if board[i][j] in used: return False + used.add(board[i][j]) + return True + + def columnIsValid(i): + used = set() + for j in range(N): + if board[j][i]=='.': continue + if board[j][i] in used: return False + used.add(board[j][i]) + return True + + def isBoxValid(i1, i2, j1, j2): + used = set() + for i in range(i1, i2+1): + for j in range(j1, j2+1): + if board[i][j]=='.': continue + if board[i][j] in used: return False + used.add(board[i][j]) + return True + + N = 9 + for i in range(N): + if not rowIsValid(i): return False + if not columnIsValid(i): return False + + for i in range(0, N, 3): + for j in range(0, N, 3): + if not isBoxValid(i, i+2, j, j+2): return False + + return True \ No newline at end of file diff --git a/problems/python3/validate-binary-search-tree.py b/problems/python3/validate-binary-search-tree.py new file mode 100755 index 0000000..4185bc6 --- /dev/null +++ b/problems/python3/validate-binary-search-tree.py @@ -0,0 +1,24 @@ +""" +The inorder travsersal of a BST is always increasing. + +Time: O(N) +Space: O(N) +""" +class Solution: + def isValidBST(self, root: Optional[TreeNode]) -> bool: + lastVal = float('-inf') + stack = [] + + node = root + while stack or node: + while node: + stack.append(node) + node = node.left + + node = stack.pop() + + if not lastVal None: + def bfs(i0, j0) -> None: + q = collections.deque([(i0+1, j0, 1), (i0-1, j0, 1), (i0, j0+1, 1), (i0, j0-1, 1)]) + visited = set() + + while q: + i, j, dis = q.popleft() + + if i<0 or j<0 or i>=MAX_ROW or j>=MAX_COL: continue + if rooms[i][j]==0 or rooms[i][j]==-1: continue + if (i, j, dis) in visited: continue + visited.add((i, j, dis)) + + if dis bool: + def dfs(i): + if i==len(s): return True + if i in history and not history[i]: return False + + for word in wordDict: + if i+len(word)<=len(s) and s[i:i+len(word)]==word: + history[i] = True + if dfs(i+len(word)): return True + history[i] = False + return False + + history = {} + return dfs(0) \ No newline at end of file diff --git a/problems/python3/word-ladder.py b/problems/python3/word-ladder.py new file mode 100644 index 0000000..453fb4a --- /dev/null +++ b/problems/python3/word-ladder.py @@ -0,0 +1,40 @@ +""" +Time: O(NxM^2). N is the number of words. M is the length of the word. +Note that, getPatterns() takes O(M^2) since creating new string will also takes O(M) and for each word we do that O(M) times. + +Space: O(NxM^2) +""" +class Solution: + def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: + def getPatterns(word) -> List[str]: + patterns = [] + for i in range(len(word)): + pattern = word[:i]+'*'+word[i+1:] + patterns.append(pattern) + return patterns + + + if endWord not in wordList: return 0 + wordList.append(beginWord) + + #build adjacency list + nei = collections.defaultdict(list) + for word in wordList: + for pattern in getPatterns(word): + nei[pattern].append(word) + + #BFS + q = collections.deque([(beginWord, 1)]) + visited = set() + while q: + word, steps = q.popleft() + + if word in visited: continue + visited.add(word) + if word==endWord: return steps + + for pattern in getPatterns(word): + for nextWord in nei[pattern]: + if nextWord in visited: continue + q.append((nextWord, steps+1)) + return 0 \ No newline at end of file diff --git a/problems/python3/word-search-ii.py b/problems/python3/word-search-ii.py new file mode 100644 index 0000000..a285655 --- /dev/null +++ b/problems/python3/word-search-ii.py @@ -0,0 +1,54 @@ +""" +Time: O(M * 4 * 3^(L-1)), M is board elements count, L is the average word length. +Space: O(N), N is the number of char in the words + +This solution will TLE. We need to further "prune" the trie once we reach the leaf node. +""" +class TrieNode(): + def __init__(self): + self.children = {} + self.isEnd = False + def addWord(self, word): + curr = self + for c in word: + if c not in curr.children: + curr.children[c] = TrieNode() + curr = curr.children[c] + curr.isEnd = True + +class Solution: + def findWords(self, board: List[List[str]], words: List[str]) -> List[str]: + def dfs(r, c, node, word): + if c<0 or r<0 or c==COLS or r==ROWS: return + if board[r][c] not in node.children: return + if (r, c) in visited: return + + visited.add((r, c)) + node = node.children[board[r][c]] + word += board[r][c] + if node.isEnd: + ans.add(word) + node.isEnd = False + + dfs(r+1, c, node, word) + dfs(r-1, c, node, word) + dfs(r, c+1, node, word) + dfs(r, c-1, node, word) + + visited.remove((r, c)) + + + root = TrieNode() + for word in words: + root.addWord(word) + + ROWS = len(board) + COLS = len(board[0]) + + visited = set() + ans = set() + node = root + for r in range(ROWS): + for c in range(COLS): + dfs(r, c, root, '') + return ans \ No newline at end of file diff --git a/problems/python3/word-search.py b/problems/python3/word-search.py new file mode 100644 index 0000000..b7ebb09 --- /dev/null +++ b/problems/python3/word-search.py @@ -0,0 +1,27 @@ +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + def helper(x, i, j): + if i<0 or i>=N or j<0 or j>=M: return False + if (i, j) in usedWords: return False + usedWords.add((i, j)) + + if word[x]==board[i][j]: + if x==len(word)-1: + return True + else: + if helper(x+1, i+1, j): return True + if helper(x+1, i, j+1): return True + if helper(x+1, i-1, j): return True + if helper(x+1, i, j-1): return True + + + usedWords.remove((i, j)) + return False + + usedWords = set() + N = len(board) + M = len(board[0]) + for i in range(N): + for j in range(M): + if helper(0, i, j): return True + return False \ No newline at end of file diff --git a/problems/redundant-connection.py b/problems/redundant-connection.py deleted file mode 100644 index 3f7d013..0000000 --- a/problems/redundant-connection.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -We use the `roots` to store the root of each node. -For example, at index 1 value is 5, means that node1's root is node5. - -We use `find()` to find the node's root. In other words, the node's parent's parent's ... -Every node we encounter along the way, we update their root value in `roots` to the up most root. (This technique is called path compression). - -So, for every edge, we unify `u` and `v` them. #[1] -Which means u and v and all of their parents all lead to one root. - -If u's root (`ur`) and v's root (`vr`) are already the same before we unify them. -This edge is redundant. - -This algorithm is called Union Find, often used in undirected graph cycle detect or grouping. -If you wanted to detect cycles in directed graph, you need to use Topological sort. -""" -class Solution(object): - def findRedundantConnection(self, edges): - def find(x): - if x != roots[x]: - roots[x] = find(roots[x]) - return roots[x] - - opt = [] - roots = range(len(edges)) - - for u, v in edges: - # union - ur = find(u) - vr = find(v) - - if ur == vr: #[2] - opt = [u, v] - else: - roots[vr] = ur #[1] - - return opt \ No newline at end of file diff --git a/problems/same-tree.py b/problems/same-tree.py deleted file mode 100644 index e17c40c..0000000 --- a/problems/same-tree.py +++ /dev/null @@ -1,23 +0,0 @@ -from collections import deque - -class Solution(object): - def isSameTree(self, p, q): - q1 = deque([p]) - q2 = deque([q]) - while q1 or q2: - if not q1 or not q2: return False - n1 = q1.popleft() - n2 = q2.popleft() - - if n1 and n2: - if n1.val!=n2.val: return False - q1.append(n1.left) - q1.append(n1.right) - q2.append(n2.left) - q2.append(n2.right) - elif n1 and not n2: - return False - elif not n1 and n2: - return False - return True - diff --git a/problems/sliding-window-maximum.py b/problems/sliding-window-maximum.py deleted file mode 100644 index a8e48a4..0000000 --- a/problems/sliding-window-maximum.py +++ /dev/null @@ -1,19 +0,0 @@ -class Solution(object): - def maxSlidingWindow(self, nums, k): - opt = [] - q = collections.deque() - for i in xrange(len(nums)): - n = nums[i] - - #move the window - if q and q[0]<=i-k: q.popleft() - - #pop the right if the element in queue is not greater than the in-coming one - #by doing this, we can always keep the max in the current window at left most - while q and nums[q[-1]]<=n: q.pop() - - q.append(i) - - #add the max to the output array after the first kth element - if 1+i>=k: opt.append(nums[q[0]]) - return opt \ No newline at end of file diff --git a/problems/top-k-frequent-elements.py b/problems/top-k-frequent-elements.py deleted file mode 100644 index 1c5b9d7..0000000 --- a/problems/top-k-frequent-elements.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -In both solution, the `counter` counts how many of each num in nums, as `counter[num]`=`num's count` -The bottle neck is we need to get k nums which has largest count (in an efficient way). -And here is how `Bucket Sort` and `Heap` comes in. - -* Bucket Sort can sort things really quick, the trade off is we need to use lots of space. -Now, because we know that the max possible count of each `num` is the count of `nums`. -We can make an list (`bucket`), where index 0~`len(nums)`, representing the count. -Every index `i` stores a list. In the list, all `num` has exactly i count. -Next we can iterate backward, from large index to small index (from higher count to lower count), until we got k element in the output. - -* Heap. We use the counter to build a max heap. Then we pop out the num which has higher count. - -Time space analysis. -* Bucket Sort. For time complexity, build the counter takes O(N), build the bucket also takes O(N). -Getting the k num which has the highest count also takes O(N). So time complexity, is O(N). -Space complexity is O(N). For buckets at most takes every element in `nums`. - -* Heap. For time complexity, build the counter takes O(N), build the heap also takes O(N). -(Yeah, I know. Some People thinks `heapify` takes O(NLogN) but it actually takes O(N). But that is another story...) -Pop out from heap takes O(N) and we do that k times, kLogN. -So time complexity, is O(N+kLogN). -Space complexity is O(N). -""" - - -from collections import Counter, defaultdict -import heapq - -# bucket sort -class Solution(object): - def topKFrequent(self, nums, k): - opt = [] - counter = collections.Counter(nums) - bucket = collections.defaultdict(list) - - for num, count in counter.items(): - bucket[count].append(num) - - for i in reversed(xrange(len(nums)+1)): - if i in bucket: - opt.extend(bucket[i]) - if len(opt)>=k: break - - return opt[:k] - -# heap -class Solution(object): - def topKFrequent(self, nums, k): - opt = [] - counter = collections.Counter(nums) - - heap = [(-count, num) for num, count in counter.items()] - heapq.heapify(heap) - - while len(opt)=0: opt.append((i-1, j)) - if j-1>=0: opt.append((i, j-1)) - return opt - - def dfs(i, j, l, word): - if word in found: return - if board[i][j]!=word[l]: return - - if l==len(word)-1 and board[i][j]==word[l]: - opt.append(word) - found.add(word) - return - - char = board[i][j] - board[i][j] = '#' - - for ni, nj in getNeighbor(i, j): - dfs(ni, nj, l+1, word) - - board[i][j] = char - - opt = [] - M = len(board) - N = len(board[0]) - found = set() - - for i in xrange(M): - for j in xrange(N): - for word in words: - if word not in found: - dfs(i, j, 0, word) - return opt - - -board = [ - ['o','a','a','n'], - ['e','t','a','e'], - ['i','h','k','r'], - ['i','f','l','v'] -] -words = ["oath","pea","eat","rain"] - -print Solution().findWords(board, words)