├── .gitignore ├── README.md ├── algorithms └── greedy.md ├── assets ├── 0401_0.png ├── 06.max-chunks-to-make-sorted-ii-00.excalidraw ├── 06.max-chunks-to-make-sorted-ii-00.png ├── 06.max-chunks-to-make-sorted-ii-01.excalidraw ├── 06.max-chunks-to-make-sorted-ii-01.png ├── 06.max-chunks-to-make-sorted-ii-02.excalidraw ├── 06.max-chunks-to-make-sorted-ii-02.png ├── 07.swap-nodes-in-pairs-00.png ├── 07.swap-nodes-in-pairs-01.png ├── 07.swap-nodes-in-pairs-02.excalidraw ├── 07.swap-nodes-in-pairs-02.png ├── 07.swap-nodes-in-pairs.excalidraw ├── 1052_0.excalidraw ├── 1052_0.png ├── 17.11_0.excalidraw ├── 17.11_0.png ├── 17.11_1.excalidraw ├── 17.11_1.png ├── 26_0.excalidraw ├── 26_0.png ├── 30_0.excalidraw.json ├── 30_0.png ├── 32_0.excalidraw.json ├── 32_0.png ├── 32_1.excalidraw.json ├── 32_1.png ├── 3_0.excalidraw ├── 3_0.png ├── 42_0.excalidraw ├── 42_0.png ├── 435_0.excalidraw ├── 435_0.png ├── 447_0.excalidraw ├── 447_0.png ├── 513_0.png ├── 513_1.excalidraw ├── 513_1.png ├── 581_0.excalidraw ├── 581_0.png ├── 74_0.excalidraw ├── 74_0.png ├── 821_0.excalidraw ├── 821_0.png ├── 821_1.excalidraw ├── 821_1.png ├── 876_0.excalidraw ├── 876_0.png ├── 987_0.excalidrawlib ├── 987_0.png ├── LRU_0.png ├── LRU_1.png ├── LRU_2.png ├── LRU_3.png ├── LRU_4.png ├── LRU_5.png ├── construct_binary_tree.png ├── custom_stack.png ├── decode_string_stack.png ├── decode_string_tree.png ├── dp.excalidraw.json ├── dp.png ├── dutch_national_flag.png ├── first_node_of_loop_in_linked_list_0.png ├── first_node_of_loop_in_linked_list_1.png ├── first_node_of_loop_in_linked_list_2.png ├── first_node_of_loop_in_linked_list_3.png ├── hashmap_2.png ├── insert-o(1).png ├── insert-o(n).png ├── intersection_of_linked_lists.png ├── intersection_of_linked_lists_1.png ├── maximum_length_of_a_binary_tree_0.png ├── maximum_length_of_a_binary_tree_1.png ├── remove-last.png ├── remove-o(1).png ├── remove-o(n).png ├── remove_swap.png ├── reverse-a-linked-list-loop.png ├── reverse-a-linked-list-recursive.png ├── shortest-distance-to-a-character.png └── update_hashmap.png ├── basic ├── array-stack-queue │ ├── 01.plus-one.md │ ├── 02.shortest-distance-to-a-character.md │ ├── 03.design-a-stack-with-increment-operation.md │ ├── 04.decode-string.md │ ├── 05.implement-queue-using-stacks.md │ ├── 06.max-chunks-to-make-sorted-ii.md │ ├── ext-implement-strstr.md │ ├── ext-insert-delete-getrandom-o1.md │ ├── ext-remove-k-digits.md │ └── ext-sort-colors.md ├── binary-tree │ ├── 13.maximum-depth-of-binary-tree.md │ ├── 14.same-tree.md │ ├── 15.sum-root-to-leaf-numbers.md │ ├── 16.find-bottom-left-tree-value.md │ ├── 17.serialize-and-deserialize-binary-tree.md │ ├── 18.vertical-order-traversal-of-a-binary-tree.md │ ├── ext-binary-tree-paths.md │ ├── ext-construct-binary-tree-from-preorder-and-inorder-traversal.md │ ├── ext-path-sum.md │ └── ext-sum-of-left-leaves.md ├── hashmap │ ├── 19.two-sum.md │ ├── 20.top-k-frequent-elements.md │ ├── 21.number-of-boomerangs.md │ ├── 22.longest-substring-without-repeating-characters.md │ ├── 23.substring-with-concatenation-of-all-words.md │ ├── 24.sudoku-solver.md │ ├── ext-kth-largest-element-in-an-array.md │ ├── ext-route-between-nodes-lcci.md │ └── ext-set-mismatch.md ├── linked-list │ ├── 07.swap-nodes-in-pairs.md │ ├── 08.rotate-list.md │ ├── 09.convert-sorted-list-to-binary-search-tree.md │ ├── 10.intersection-of-two-linked-lists.md │ ├── 11.linked-list-cycle-ii.md │ ├── 12.lru-cache.md │ ├── ext-merge-two-sorted-lists.md │ ├── ext-remove-duplicates-from-sorted-list.md │ └── ext-reverse-linked-list.md └── two-pointers │ ├── 25.search-insert-position.md │ ├── 26.search-a-2d-matrix.md │ ├── 27.remove-duplicates-from-sorted-array.md │ ├── 28.middle-of-the-linked-list.md │ ├── 29.grumpy-bookstore-owner.md │ ├── 30.sliding-window-maximum.md │ ├── ext-find-closest-lcci.md │ ├── ext-koko-eating-bananas.md │ ├── ext-trapping-rain-water.md │ └── ext-two-sum-ii-input-array-is-sorted.md ├── extensions ├── 04.09.bst-sequences-lcci.md ├── 77.combination.md ├── 925.long-pressed-name.md └── lcof_51-II.和为s的连续正数序列.md ├── medium ├── day-41.md ├── day-42.md ├── day-43.md ├── day-44.md ├── day-45.md ├── day-46.md ├── day-47.md ├── day-48.md ├── day-49.md ├── day-50.md ├── day-51.md ├── day-52.md ├── day-53.md └── hot │ ├── 34.traversal-of-binary-tree.md │ ├── 35.reverse-linked-list-ii.md │ ├── 35.reverse-linked-list.md │ ├── 35.reverse-nodes-in-k-group.md │ ├── 36.subsets.md │ ├── 37.unique-paths.md │ ├── 38.longest-valid-parentheses.md │ ├── 38.valid-parentheses.md │ ├── 40.find-the-longest-substring-containing-vowels-in-even-counts.md │ ├── 40.netease.md │ └── 40.subarray-sum-equals-k.md └── topics ├── day-62.md ├── day-63.md ├── day-64.md ├── day-68.md ├── day-69.md ├── day-74.md ├── day-79.md ├── day-80.md ├── day-81.md ├── day-82.md ├── day-83.md ├── day-84.md ├── day-85.md ├── day-86.md ├── day-87.md ├── day-88.md ├── day-89.md ├── day-90.md ├── day-91.md ├── greedy ├── ext-assign-cookies.md ├── ext-can-place-flowers.md ├── ext-candy.md ├── ext-largest-perimeter-triangle.md ├── ext-minimum-number-of-arrows-to-burst-balloons.md └── ext-non-overlapping-intervals.md └── sliding-window └── ext-shortest-unsorted-continuous-subarray.md /.gitignore: -------------------------------------------------------------------------------- 1 | **.local.** -------------------------------------------------------------------------------- /algorithms/greedy.md: -------------------------------------------------------------------------------- 1 | # 贪心算法 2 | 3 | ## 简介 4 | 5 | 贪心算法就是,在每次操作时都做出**当下最好**的选择,从而使最后得到的结果是最优解。做出局部选择时,**不考虑这个选择对将来的影响**,而且做出选择之后**不会回退到以前**。因为全局结果是局部结果的简单求和,且**局部结果互不相干**,所以**局部最优策略**也就是全局最优策略。 6 | 7 | 但并不是所有问题都可以使用贪心算法得到最优解,重点是**贪婪策略**的选择,难点在于如何证明局部最优解就可以得到全局最优解。 8 | 9 | 贪心算法有点像动态规划,只不过,每个局部选择都只依靠另**一个**局部选择。要证明贪心算法能够得到全局最优解,只需要证明在填表 (动态规划的状态表) 的时候,每填一个格子我们只需要查询另一个格子,而不需要综合考虑几个格子。因为贪心算法每次只考虑一个子问题,所以解决问题的时间复杂度一般是线性的。 10 | 11 | ## 算法 12 | 13 | - 初始化结果数组 14 | - 在每一步中,把当前项加入结果数组中 15 | - 如果此时结果数组是一个可行解,那就保留当前项 16 | - 如果不是,那就去除当前项,并且之后不再考虑这一项 17 | 18 | ## 相关题目类型 19 | 20 | ### 分配问题 21 | 22 | - [455.分发饼干](https://github.com/suukii/91-days-algorithm/blob/master/topics/greedy/ext-assign-cookies.md) 23 | - [135.分发糖果](https://github.com/suukii/91-days-algorithm/blob/master/topics/greedy/ext-candy.md) 24 | 25 | ### 区间问题 26 | 27 | - [435.无重叠区间](https://github.com/suukii/91-days-algorithm/blob/master/topics/greedy/ext-non-overlapping-intervals.md) 28 | 29 | TODO 30 | 31 | - [ ] http://web.stanford.edu/class/archive/cs/cs161/cs161.1176/Lectures/CS161Lecture14.pdf 32 | - [ ] http://web.stanford.edu/class/archive/cs/cs161/cs161.1176/Sections/final_review-1.pdf 33 | - [ ] file:///H:/Algorithm/LeetCode%20101%20-%20A%20LeetCode%20Grinding%20Guide%20(C++%20Version).pdf 34 | -------------------------------------------------------------------------------- /assets/0401_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/0401_0.png -------------------------------------------------------------------------------- /assets/06.max-chunks-to-make-sorted-ii-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/06.max-chunks-to-make-sorted-ii-00.png -------------------------------------------------------------------------------- /assets/06.max-chunks-to-make-sorted-ii-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/06.max-chunks-to-make-sorted-ii-01.png -------------------------------------------------------------------------------- /assets/06.max-chunks-to-make-sorted-ii-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/06.max-chunks-to-make-sorted-ii-02.png -------------------------------------------------------------------------------- /assets/07.swap-nodes-in-pairs-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/07.swap-nodes-in-pairs-00.png -------------------------------------------------------------------------------- /assets/07.swap-nodes-in-pairs-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/07.swap-nodes-in-pairs-01.png -------------------------------------------------------------------------------- /assets/07.swap-nodes-in-pairs-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/07.swap-nodes-in-pairs-02.png -------------------------------------------------------------------------------- /assets/1052_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/1052_0.png -------------------------------------------------------------------------------- /assets/17.11_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/17.11_0.png -------------------------------------------------------------------------------- /assets/17.11_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/17.11_1.png -------------------------------------------------------------------------------- /assets/26_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/26_0.png -------------------------------------------------------------------------------- /assets/30_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/30_0.png -------------------------------------------------------------------------------- /assets/32_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/32_0.png -------------------------------------------------------------------------------- /assets/32_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/32_1.png -------------------------------------------------------------------------------- /assets/3_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/3_0.png -------------------------------------------------------------------------------- /assets/42_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/42_0.png -------------------------------------------------------------------------------- /assets/435_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/435_0.png -------------------------------------------------------------------------------- /assets/447_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/447_0.png -------------------------------------------------------------------------------- /assets/513_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/513_0.png -------------------------------------------------------------------------------- /assets/513_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/513_1.png -------------------------------------------------------------------------------- /assets/581_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/581_0.png -------------------------------------------------------------------------------- /assets/74_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/74_0.png -------------------------------------------------------------------------------- /assets/821_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/821_0.png -------------------------------------------------------------------------------- /assets/821_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/821_1.png -------------------------------------------------------------------------------- /assets/876_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/876_0.png -------------------------------------------------------------------------------- /assets/987_0.excalidrawlib: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidrawlib", 3 | "version": 1, 4 | "library": [] 5 | } -------------------------------------------------------------------------------- /assets/987_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/987_0.png -------------------------------------------------------------------------------- /assets/LRU_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/LRU_0.png -------------------------------------------------------------------------------- /assets/LRU_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/LRU_1.png -------------------------------------------------------------------------------- /assets/LRU_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/LRU_2.png -------------------------------------------------------------------------------- /assets/LRU_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/LRU_3.png -------------------------------------------------------------------------------- /assets/LRU_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/LRU_4.png -------------------------------------------------------------------------------- /assets/LRU_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/LRU_5.png -------------------------------------------------------------------------------- /assets/construct_binary_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/construct_binary_tree.png -------------------------------------------------------------------------------- /assets/custom_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/custom_stack.png -------------------------------------------------------------------------------- /assets/decode_string_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/decode_string_stack.png -------------------------------------------------------------------------------- /assets/decode_string_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/decode_string_tree.png -------------------------------------------------------------------------------- /assets/dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/dp.png -------------------------------------------------------------------------------- /assets/dutch_national_flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/dutch_national_flag.png -------------------------------------------------------------------------------- /assets/first_node_of_loop_in_linked_list_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/first_node_of_loop_in_linked_list_0.png -------------------------------------------------------------------------------- /assets/first_node_of_loop_in_linked_list_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/first_node_of_loop_in_linked_list_1.png -------------------------------------------------------------------------------- /assets/first_node_of_loop_in_linked_list_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/first_node_of_loop_in_linked_list_2.png -------------------------------------------------------------------------------- /assets/first_node_of_loop_in_linked_list_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/first_node_of_loop_in_linked_list_3.png -------------------------------------------------------------------------------- /assets/hashmap_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/hashmap_2.png -------------------------------------------------------------------------------- /assets/insert-o(1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/insert-o(1).png -------------------------------------------------------------------------------- /assets/insert-o(n).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/insert-o(n).png -------------------------------------------------------------------------------- /assets/intersection_of_linked_lists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/intersection_of_linked_lists.png -------------------------------------------------------------------------------- /assets/intersection_of_linked_lists_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/intersection_of_linked_lists_1.png -------------------------------------------------------------------------------- /assets/maximum_length_of_a_binary_tree_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/maximum_length_of_a_binary_tree_0.png -------------------------------------------------------------------------------- /assets/maximum_length_of_a_binary_tree_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/maximum_length_of_a_binary_tree_1.png -------------------------------------------------------------------------------- /assets/remove-last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/remove-last.png -------------------------------------------------------------------------------- /assets/remove-o(1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/remove-o(1).png -------------------------------------------------------------------------------- /assets/remove-o(n).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/remove-o(n).png -------------------------------------------------------------------------------- /assets/remove_swap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/remove_swap.png -------------------------------------------------------------------------------- /assets/reverse-a-linked-list-loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/reverse-a-linked-list-loop.png -------------------------------------------------------------------------------- /assets/reverse-a-linked-list-recursive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/reverse-a-linked-list-recursive.png -------------------------------------------------------------------------------- /assets/shortest-distance-to-a-character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/shortest-distance-to-a-character.png -------------------------------------------------------------------------------- /assets/update_hashmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leetcode-pp/91-days-algorithm/0074cb1c2cdfe74f0ba8e01246404c6f5acecf39/assets/update_hashmap.png -------------------------------------------------------------------------------- /basic/array-stack-queue/01.plus-one.md: -------------------------------------------------------------------------------- 1 | # 66.加一 2 | 3 | https://leetcode-cn.com/problems/plus-one 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 9 | 10 | 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 11 | 12 | 你可以假设除了整数 0 之外,这个整数不会以零开头。 13 | 14 | 示例 1: 15 | 16 | 输入: [1,2,3] 17 | 输出: [1,2,4] 18 | 解释: 输入数组表示数字 123。 19 | 示例 2: 20 | 21 | 输入: [4,3,2,1] 22 | 输出: [4,3,2,2] 23 | 解释: 输入数组表示数字 4321。 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode-cn.com/problems/plus-one 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | ``` 29 | 30 | ## 思路 31 | 32 | 在数组上做竖式加法,用 `carry` 来表示进位,反向遍历数组即可。 33 | 34 | 遍历结束条件: 35 | 36 | 1. 数组所有元素都遍历过了 37 | 2. 当 `carry` 为 0 的时候 38 | 39 | 需要注意的点: 40 | 41 | 如果遍历结束后 `carry` 大于 0,还需要在数组前面补一位。 42 | 43 | ## 复杂度 44 | 45 | - 时间复杂度:$O(N)$, N 为数组长度。 46 | - 空间复杂度:$O(1)$。 47 | 48 | ## 代码 49 | 50 | ```js 51 | /** 52 | * @param {number[]} digits 53 | * @return {number[]} 54 | */ 55 | var plusOne = function (digits) { 56 | let carry = 1, 57 | sum = 0, 58 | index = digits.length - 1; 59 | 60 | while (carry > 0 && index > -1) { 61 | sum = digits[index] + 1; 62 | carry = Math.floor(sum / 10); 63 | digits[index] = sum % 10; 64 | index--; 65 | } 66 | 67 | carry && digits.unshift(carry); 68 | 69 | return digits; 70 | }; 71 | ``` 72 | -------------------------------------------------------------------------------- /basic/array-stack-queue/04.decode-string.md: -------------------------------------------------------------------------------- 1 | # 394.字符串解码 2 | 3 | https://leetcode-cn.com/problems/decode-string/ 4 | 5 | - [394.字符串解码](#394dot字符串解码) 6 | - [题目描述](#题目描述) 7 | - [方法 1: 递归](#方法-1-递归) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | - [方法 2: 循环 + 栈](#方法-2-循环--栈) 12 | - [图解](#图解) 13 | - [复杂度分析](#复杂度分析-1) 14 | - [代码](#代码-1) 15 | 16 | ## 题目描述 17 | 18 | ``` 19 | 给定一个经过编码的字符串,返回它解码后的字符串。 20 | 21 | 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 22 | 23 | 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 24 | 25 | 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。 26 | 27 | 示例: 28 | 29 | s = "3[a]2[bc]", 返回 "aaabcbc". 30 | s = "3[a2[c]]", 返回 "accaccacc". 31 | s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef". 32 | 33 | 来源:力扣(LeetCode) 34 | 链接:https://leetcode-cn.com/problems/decode-string 35 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 36 | ``` 37 | 38 | ## 方法 1: 递归 39 | 40 | ### 思路 41 | 42 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/decode_string_tree.png) 43 | 44 | `n[string]` 表示解析 `[]` 模板里面的内容,然后重复 `n` 次,即得到 n 个 string 拼接起来的字符串。 45 | 46 | 根据题意,`[]` 里面也是可以嵌套 `[]` 的,例如 `n[m[string]]`。这种情况下,我们得先解析最内层的模板,重复 `m` 次,然后将 `m * string` 的结果作为外层模板的解析内容,再重复 `n` 次。 47 | 48 | 如果嵌套的层数更多,我们也是得先找到最内层的 `[]`,就像洋葱一样,一层一层地剥开,然后再从内到外一层一层地解析和拼接。这种描述很容易就让人想到了递归。 49 | 50 | 看代码注释吧。 51 | 52 | ### 复杂度分析 53 | 54 | - 时间复杂度:$O(S)$,S 是解析后字符串的长度。 55 | - 空间复杂度:$O(S)$,S 是解析后字符串的长度,递归栈空间。 56 | 57 | ### 代码 58 | 59 | ```js 60 | const type = { 61 | isAlpha: s => /[a-z]/i.test(s), 62 | isDigit: s => /[0-9]/.test(s), 63 | isOpenParen: s => s === '[', 64 | isCloseParen: s => s === ']', 65 | }; 66 | 67 | /** 68 | * @param {string} s 69 | * @return {string} 70 | */ 71 | var decodeString = function (s, i = 0) { 72 | // 从 i 开始遍历字符串 73 | 74 | let decoded = ''; // 解密字符串 75 | let cnt = ''; // 累计次数 76 | 77 | while (i < s.length) { 78 | if (type.isAlpha(s[i])) { 79 | // 普通字符,直接拼接到 decoded 80 | decoded += s[i]; 81 | i++; 82 | } else if (type.isDigit(s[i])) { 83 | // 数字,拼接到 cnt 84 | cnt += s[i]; 85 | i++; 86 | } else if (type.isOpenParen(s[i])) { 87 | // 遇到开括号,就把括号内的字符串重复 cnt 次,再拼接到 decoded 88 | // 但括号内可能存在嵌套括号,所以需要递归处理 89 | // 我们需要从递归中取两个东西,1.括号内解析后的模式,2.这个开括号对应的右括号的下标,下次遍历字符串就从这个下标+1开始 90 | const [pattern, nextIndex] = decodeString(s, i + 1); 91 | // 重复 cnt 次拼接到 decoded 92 | decoded += pattern.repeat(Number(cnt)); 93 | 94 | cnt = ''; 95 | i = nextIndex; 96 | continue; 97 | } else if (type.isCloseParen(s[i])) { 98 | // 遇到闭括号,说明括号内的模式解析完毕 99 | // 递归结束,返回我们需要的东西:1.解析后的字符串,2.解析到的字符下标 100 | return [decoded, i + 1]; 101 | } 102 | } 103 | return decoded; 104 | }; 105 | ``` 106 | 107 | ## 方法 2: 循环 + 栈 108 | 109 | 可以用递归解决的问题,也可以用循环来解决。 110 | 111 | 这里我用了正则 `/[a-zA-Z]+|[0-9]+|\[|\]/` 和 `exec()` 方法来遍历字符串并把它们拆分成 `token`,比如,`lz3[ab2[c]]` 会被拆分成 `lz`, `3`, `[`, `ab`, `2`, `[`, `c`, `]`, `]`。 112 | 113 | 1. 遇到字母块 (`lz`)、数字时,入栈; 114 | 2. 遇到 `[` 时,入栈,用来标识当前进入一个模板解析了; 115 | 3. 遇到 `]` 时,说明当前模板遍历完了,我们可以开始解析了。开始出栈,把出栈的字母块都拼接起来,等出栈到 `[` 时,说明当前模板解析完成了。继续出栈一个元素,这个元素就是当前模板要重复的次数,把"字母块 \* 次数"后推入栈中。之所以要推入栈中是因为模板是可以嵌套的,当前模板的外层可以还是一个模板,所以我们要把结果放回去,继续解析外层的模板。 116 | 117 | ### 图解 118 | 119 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/decode_string_stack.png) 120 | 121 | ### 复杂度分析 122 | 123 | - 时间复杂度:$O(S)$,S 是解析后字符串的长度。 124 | - 空间复杂度:$O(S)$,S 是解析后字符串的长度。 125 | 126 | ### 代码 127 | 128 | ```js 129 | /** 130 | * @param {string} s 131 | * @return {string} 132 | */ 133 | var decodeString = function (s) { 134 | const reg = /[a-zA-Z]+|[0-9]+|\[|\]/g; 135 | const stack = []; 136 | const peek = () => stack[stack.length - 1]; // p.s. 不正经栈 137 | 138 | while (reg.lastIndex < s.length) { 139 | let token = reg.exec(s)[0]; 140 | if (token !== ']') { 141 | // 数字,字母,左括号通通入栈 142 | stack.push(token); 143 | } else { 144 | // 遇到右括号就开始出栈 145 | let str = ''; 146 | // [] 中间的就是要重复的模式,把它们全部出栈,拼接起来 147 | while (peek() !== '[') { 148 | str = stack.pop() + str; 149 | } 150 | // 丢掉左括号 151 | stack.pop(); 152 | // 左括号前面的一定是模式重复的次数 153 | const num = +stack.pop(); 154 | // 把复制操作后的字符串放回栈中,作为外层 [] 模式的一部分 155 | stack.push(str.repeat(num)); 156 | } 157 | } 158 | return stack.join(''); 159 | }; 160 | ``` 161 | -------------------------------------------------------------------------------- /basic/array-stack-queue/05.implement-queue-using-stacks.md: -------------------------------------------------------------------------------- 1 | # 232.用栈实现队列 2 | 3 | https://leetcode-cn.com/problems/implement-queue-using-stacks/ 4 | 5 | - [232.用栈实现队列](#232dot用栈实现队列) 6 | - [题目描述](#题目描述) 7 | - [方法 1](#方法-1) 8 | - [思路](#思路) 9 | - [复杂度](#复杂度) 10 | - [代码](#代码) 11 | - [方法 2](#方法-2) 12 | - [思路](#思路-1) 13 | - [复杂度](#复杂度-1) 14 | - [代码](#代码-1) 15 | 16 | ## 题目描述 17 | 18 | ``` 19 | 使用栈实现队列的下列操作: 20 | 21 | push(x) -- 将一个元素放入队列的尾部。 22 | pop() -- 从队列首部移除元素。 23 | peek() -- 返回队列首部的元素。 24 | empty() -- 返回队列是否为空。 25 | 示例: 26 | 27 | MyQueue queue = new MyQueue(); 28 | 29 | queue.push(1); 30 | queue.push(2); 31 | queue.peek(); // 返回 1 32 | queue.pop(); // 返回 1 33 | queue.empty(); // 返回 false 34 | 说明: 35 | 36 | 你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。 37 | 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 38 | 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。 39 | 40 | 41 | 来源:力扣(LeetCode) 42 | 链接:https://leetcode-cn.com/problems/implement-queue-using-stacks 43 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 44 | ``` 45 | 46 | ## 方法 1 47 | 48 | ### 思路 49 | 50 | 由于队列是 FIFI (先进先出),而栈是 FILO (先进后出),如果要用栈来模拟队列,那么每次往队列尾端增加元素的时候,这个元素需要放在栈底,因为它是最后才会出列。 51 | 52 | 方法之一是,每次往队列尾端 `push` 一个新元素时,我们都先把栈中的元素尽数清空,然后把新元素入栈,再把刚刚清出来的元素放回栈中,所以还需要一个辅助栈来暂存清出来的元素。 53 | 54 | ### 复杂度 55 | 56 | - 时间复杂度:入列操作是 $O(n)$,每次入列时,除新增元素外,每个元素都需要分别出栈入栈 2 次 (从模拟队列的栈中弹出,压入辅助栈,再从辅助栈弹出,压入队列模拟栈)。压入、弹出操作的时间复杂度都是 $O(1)$,所以总的时间复杂度差不多是 $O(4n)$,忽略掉常数,最后得到 $O(n)$。出列操作是 $O(1)$。 57 | - 空间复杂度:$O(n)$,n 是队列的大小,需要一个大小为 n 的栈来模拟队列,还需要一个大小为 n 的辅助空间,但总的空间复杂度还是 $O(n)$。 58 | 59 | ### 代码 60 | 61 | JavaScript Code 62 | 63 | ```js 64 | class MyQueue { 65 | constructor() { 66 | this.stack = []; 67 | } 68 | 69 | push(x) { 70 | const helper = []; 71 | while (!this.empty()) { 72 | helper.push(this.stack.pop()); 73 | } 74 | this.stack.push(x); 75 | while (helper.length) { 76 | this.stack.push(helper.pop()); 77 | } 78 | } 79 | 80 | peek() { 81 | return this.stack[this.stack.length - 1]; 82 | } 83 | 84 | pop() { 85 | return this.stack.pop(); 86 | } 87 | 88 | empty() { 89 | return this.stack.length === 0; 90 | } 91 | } 92 | ``` 93 | 94 | ## 方法 2 95 | 96 | ### 思路 97 | 98 | 方法 1 是在元素入列的时候,就考虑好了它出列的顺序,但我们还可以在元素需要出列的时候再来考虑这个问题。这样的话: 99 | 100 | 1. 入列时直接 `push` 到模拟栈中; 101 | 2. 出列时,由于先入列的元素在栈底,需要先把其他元素弹出,依次压入辅助栈; 102 | 3. 栈底元素弹出,出列; 103 | 4. 刚才出栈的其他元素依次从辅助栈弹出,重新压入模拟栈。 104 | 105 | 再仔细想想的话: 106 | 107 | - 第 2 步中,辅助栈中的元素出栈顺序刚好就是队列的出列顺序; 108 | - 所以到第 4 步的时候,我们根本没必要把元素再从辅助栈转移到模拟栈; 109 | - 下一次 `pop` 操作时,直接从辅助栈弹出元素就可以了; 110 | - 如果辅助栈中没有元素了,我们再重复第 2 步。 111 | 112 | 这样的话,我们的队列元素其实是用了两个栈来储存,所以在判断队列是否为空的时候,两个栈都要考虑进去。 113 | 114 | ### 复杂度 115 | 116 | - 时间复杂度:入列是 $O(1)$,出列最差的情况就是每个元素都要从模拟栈中弹出,压入辅助栈,再从辅助栈中弹出,所以是 $O(n)$。 117 | - 空间复杂度:$O(n)$,n 为队列大小。 118 | 119 | ### 代码 120 | 121 | JavaScript Code 122 | 123 | ```js 124 | class MyQueue { 125 | constructor() { 126 | this.stack = new MyStack(); 127 | this.helper = new MyStack(); 128 | } 129 | 130 | push(x) { 131 | this.stack.push(x); 132 | } 133 | 134 | peek() { 135 | if (this.helper.empty()) { 136 | while (!this.stack.empty()) { 137 | this.helper.push(this.stack.pop()); 138 | } 139 | } 140 | return this.helper.peek(); 141 | } 142 | 143 | pop() { 144 | if (this.helper.empty()) { 145 | while (!this.stack.empty()) { 146 | this.helper.push(this.stack.pop()); 147 | } 148 | } 149 | return this.helper.pop(); 150 | } 151 | 152 | empty() { 153 | return this.stack.empty() && this.helper.empty(); 154 | } 155 | } 156 | 157 | class MyStack { 158 | constructor() { 159 | this.stack = []; 160 | } 161 | push(x) { 162 | this.stack.push(x); 163 | } 164 | pop() { 165 | return this.stack.pop(); 166 | } 167 | peek() { 168 | return this.stack[this.stack.length - 1]; 169 | } 170 | empty() { 171 | return this.stack.length === 0; 172 | } 173 | } 174 | ``` 175 | -------------------------------------------------------------------------------- /basic/array-stack-queue/06.max-chunks-to-make-sorted-ii.md: -------------------------------------------------------------------------------- 1 | # 768. 最多能完成排序的块 II 2 | 3 | https://leetcode-cn.com/problems/max-chunks-to-make-sorted-ii/ 4 | 5 | - [768. 最多能完成排序的块 II](#768dot-最多能完成排序的块-ii) 6 | - [题目描述](#题目描述) 7 | - [方法 1: 滑动窗口](#方法-1-滑动窗口) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | - [方法 2: 单调递增栈](#方法-2-单调递增栈) 12 | - [思路](#思路-1) 13 | - [图解](#图解) 14 | - [复杂度分析](#复杂度分析-1) 15 | - [代码](#代码-1) 16 | 17 | ## 题目描述 18 | 19 | ``` 20 | 这个问题和“最多能完成排序的块”相似,但给定数组中的元素可以重复,输入数组最大长度为2000,其中的元素最大为10**8。 21 | 22 | arr是一个可能包含重复元素的整数数组,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。 23 | 24 | 我们最多能将数组分成多少块? 25 | 26 | 示例 1: 27 | 28 | 输入: arr = [5,4,3,2,1] 29 | 输出: 1 30 | 解释: 31 | 将数组分成2块或者更多块,都无法得到所需的结果。 32 | 例如,分成 [5, 4], [3, 2, 1] 的结果是 [4, 5, 1, 2, 3],这不是有序的数组。 33 | 示例 2: 34 | 35 | 输入: arr = [2,1,3,4,4] 36 | 输出: 4 37 | 解释: 38 | 我们可以把它分成两块,例如 [2, 1], [3, 4, 4]。 39 | 然而,分成 [2, 1], [3], [4], [4] 可以得到最多的块数。 40 | 注意: 41 | 42 | arr的长度在[1, 2000]之间。 43 | arr[i]的大小在[0, 10**8]之间。 44 | 45 | 来源:力扣(LeetCode) 46 | 链接:https://leetcode-cn.com/problems/max-chunks-to-make-sorted-ii 47 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 48 | ``` 49 | 50 | ## 方法 1: 滑动窗口 51 | 52 | ### 思路 53 | 54 | 题目有一个提示: 55 | 56 | ``` 57 | Each k for which some permutation of arr[:k] is equal to sorted(arr)[:k] is where we should cut each chunk. 58 | ``` 59 | 60 | 也就是原数组进行分块后,每一个分块和排序后的数组中对应的分块数字是一样的,只是排序不同。 61 | 62 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/06.max-chunks-to-make-sorted-ii-00.png) 63 | 64 | 既然每个分块中数字是一样的,那它们的和也是一样的了。我们可以用一个滑动窗口同时扫描原数组和排序数组,当窗口中数字的和一样时,就将数组进行分块,就像上图中的色块一样。 65 | 66 | ### 复杂度分析 67 | 68 | - 时间复杂度:$O(NlogN)$,N 为数组长度,数组排序时间认为是 $NlogN$,滑动窗口遍历数组时间为 $N$。 69 | - 空间复杂度:$O(N)$,N 为数组长度。 70 | 71 | ### 代码 72 | 73 | JavaScript Code 74 | 75 | ```js 76 | /** 77 | * @param {number[]} arr 78 | * @return {number} 79 | */ 80 | var maxChunksToSorted = function (arr) { 81 | const sorted = [...arr]; 82 | sorted.sort((a, b) => a - b); 83 | 84 | let count = 0, 85 | sum1 = 0, 86 | sum2 = 0; 87 | 88 | for (let i = 0; i < arr.length; i++) { 89 | sum1 += arr[i]; 90 | sum2 += sorted[i]; 91 | 92 | if (sum1 === sum2) { 93 | count++; 94 | sum1 = sum2 = 0; // 这行不要也可以啦 95 | } 96 | } 97 | 98 | return count; 99 | }; 100 | ``` 101 | 102 | ## 方法 2: 单调递增栈 103 | 104 | ### 思路 105 | 106 | 根据题意,将原数组进行分块后,对各分块分别进行排序后的结果等于原数组排序后的结果。 107 | 108 | 可以得到的一个结论是,每个分块中的数字相对于前一个分块都是递增的(因为有重复数字,所以也可能是相同),下一个分块中的所有数字都会大于等于上一个分块中的所有数字。 109 | 110 | - 因为题目要求能分的最多的块数,所以我们在分块的时候要尽量把块分小,这样就能分得比较多。 111 | 112 | - 在遍历数组的过程中,如果一个数字比之前所有分块的最大值都要大,我们就把它作为一个新的分块。 113 | 114 | - 如果数字小于之前某些分块的最大值,那这些分块都要被合成一个分块(保持栈的单调递增)。 115 | 116 | ### 图解 117 | 118 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/06.max-chunks-to-make-sorted-ii-01.png) 119 | 120 | 再看一个例子: 121 | 122 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/06.max-chunks-to-make-sorted-ii-02.png) 123 | 124 | ### 复杂度分析 125 | 126 | - 时间复杂度:$O(N)$,N 为数组长度。 127 | - 空间复杂度:$O(N)$,N 为数组长度,单调栈消耗的空间。 128 | 129 | ### 代码 130 | 131 | JavaScript Code 132 | 133 | ```js 134 | class Stack { 135 | constructor() { 136 | this.list = []; 137 | } 138 | push(val) { 139 | this.list.push(val); 140 | } 141 | pop() { 142 | return this.list.pop(); 143 | } 144 | empty() { 145 | return this.list.length === 0; 146 | } 147 | peek() { 148 | return this.list[this.list.length - 1]; 149 | } 150 | size() { 151 | return this.list.length; 152 | } 153 | } 154 | 155 | /** 156 | * @param {number[]} arr 157 | * @return {number} 158 | */ 159 | var maxChunksToSorted = function (arr) { 160 | const stack = new Stack(); 161 | 162 | for (let i = 0; i < arr.length; i++) { 163 | if (stack.empty() || stack.peek() <= arr[i]) { 164 | stack.push(arr[i]); 165 | } else { 166 | const temp = stack.pop(); 167 | 168 | while (stack.peek() > arr[i]) { 169 | stack.pop(); 170 | } 171 | 172 | stack.push(temp); 173 | } 174 | } 175 | return stack.size(); 176 | }; 177 | ``` 178 | -------------------------------------------------------------------------------- /basic/array-stack-queue/ext-implement-strstr.md: -------------------------------------------------------------------------------- 1 | # 28. 实现 strStr() 2 | 3 | https://leetcode-cn.com/problems/implement-strstr/ 4 | 5 | - [28. 实现 strStr()](#28-实现-strstr) 6 | - [题目描述](#题目描述) 7 | - [方法 1:滑动窗口+子串逐一比较](#方法-1滑动窗口子串逐一比较) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 实现 strStr() 函数。 16 | 17 | 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。 18 | 19 | 示例 1: 20 | 21 | 输入: haystack = "hello", needle = "ll" 22 | 输出: 2 23 | 示例 2: 24 | 25 | 输入: haystack = "aaaaa", needle = "bba" 26 | 输出: -1 27 | 说明: 28 | 29 | 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 30 | 31 | 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。 32 | 33 | 来源:力扣(LeetCode) 34 | 链接:https://leetcode-cn.com/problems/implement-strstr 35 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 36 | ``` 37 | 38 | ## 方法 1:滑动窗口+子串逐一比较 39 | 40 | ### 思路 41 | 42 | 用一个 `needle` 长度的滑动窗口滑动遍历 `haystack`,每次滑动都比较窗口内的子字符串和 `needle`(其实只要窗口和 `needle` 的第一个字母相同才开始比较)。 43 | 44 | ### 复杂度分析 45 | 46 | - 时间复杂度:O$((m-n)*n)$,m 是 haystack 的长度,n 是 needle 的长度。 47 | - 空间复杂度:O$(1)$。 48 | 49 | ### 代码 50 | 51 | JavaScript Code 52 | 53 | ```js 54 | /** 55 | * @param {string} haystack 56 | * @param {string} needle 57 | * @return {number} 58 | */ 59 | var strStr = function (haystack, needle) { 60 | if (!needle) return 0; 61 | 62 | let l = 0, 63 | r = needle.length; 64 | while (r <= haystack.length) { 65 | if (haystack[l] === needle[0] && compare(haystack, needle, l, r)) 66 | return l; 67 | l++; 68 | r++; 69 | } 70 | return -1; 71 | 72 | // ******************************** 73 | function compare(long, short, l, r) { 74 | for (let i = 0; i < r - l; i++) { 75 | if (long[i + l] !== short[i]) return false; 76 | } 77 | return true; 78 | } 79 | }; 80 | ``` 81 | -------------------------------------------------------------------------------- /basic/array-stack-queue/ext-insert-delete-getrandom-o1.md: -------------------------------------------------------------------------------- 1 | # 380.常数时间插入、删除和获取随机元素 2 | 3 | https://leetcode-cn.com/problems/insert-delete-getrandom-o1/description/ 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构。 9 | 10 | insert(val):当元素 val 不存在时,向集合中插入该项。 11 | remove(val):元素 val 存在时,从集合中移除该项。 12 | getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。 13 | 示例 : 14 | 15 | // 初始化一个空的集合。 16 | RandomizedSet randomSet = new RandomizedSet(); 17 | 18 | // 向集合中插入 1 。返回 true 表示 1 被成功地插入。 19 | randomSet.insert(1); 20 | 21 | // 返回 false ,表示集合中不存在 2 。 22 | randomSet.remove(2); 23 | 24 | // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。 25 | randomSet.insert(2); 26 | 27 | // getRandom 应随机返回 1 或 2 。 28 | randomSet.getRandom(); 29 | 30 | // 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。 31 | randomSet.remove(1); 32 | 33 | // 2 已在集合中,所以返回 false 。 34 | randomSet.insert(2); 35 | 36 | // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。 37 | randomSet.getRandom(); 38 | ``` 39 | 40 | ## 思路 41 | 42 | 首先得考虑的是,用数组还是用链表来存,先来复习一下数组和链表常见操作的时间复杂度吧。 43 | 44 | ### 数组常见操作时间复杂度分析 45 | 46 | - 随机访问 -> O(1) 47 | - 插入数值到数组 -> O(N) 48 | - 插入数值到数组最后 -> O(1) 49 | - 从数组删除数值 -> O(N) 50 | - 从数组最后删除数值 -> O(1) 51 | 52 | ### 链表常见操作时间复杂度分析 53 | 54 | - 访问 -> O(N) 55 | - 插入数值到链表 -> O(N) 56 | - 插入数值到链表开头 -> O(1) 57 | - 从链表删除数值 -> O(N) 58 | - 从链表开头删除数值 -> O(1) 59 | 60 | 很显然,链表时间复杂度为 O(N) 的访问操作并不符合我们的需求,所以我们还是选择数组来作为存储数据的容器。 61 | 62 | ### 插入 63 | 64 | 首先要实现常数时间插入元素,我们只能在数组最后插入。 65 | 66 | **在数组最后插入元素** 67 | 68 | ![Untitled-2020-06-05-2052](https://user-images.githubusercontent.com/30331289/83948534-7743bf00-a850-11ea-80f4-9cd46fd75d1f.png) 69 | 70 | **在数组其他位置插入元素** 71 | 72 | ![insert-o(n)](https://user-images.githubusercontent.com/30331289/83970694-9d796580-a909-11ea-9011-1d0724a2e77a.png) 73 | 74 | ### 获取随机元素 75 | 76 | 这个就很简单了,因为数组是可以通过下标随机访问的,我们只需要生成一个 0 ~ N-1 的随机数即可,N 为数组长度。 77 | 78 | ### 删除 79 | 80 | 删除元素的操作可以分为两种: 81 | 82 | 1. 删除末尾元素,时间复杂度为 O(1) 83 | 84 | ![remove-last](https://user-images.githubusercontent.com/30331289/83971346-863c7700-a90d-11ea-8540-efc31a712086.png) 85 | 86 | 2. 删除非末尾元素,因为删除位置之后的每个元素都要向前移动一步,所以时间复杂度是 O(N) 87 | 88 | ![remove-o(n)](https://user-images.githubusercontent.com/30331289/83949462-80d02580-a856-11ea-9260-77be04319d0d.png) 89 | 90 | 显然,如果我们想实现题目要求的 O(1) 时间的删除,只能在数组末尾进行删除操作。具体做法就是把要删除的元素和末尾的元素换个位置,然后再从数组末尾删除。 91 | 92 | ![remove-o(1)](https://user-images.githubusercontent.com/30331289/83971351-8ccaee80-a90d-11ea-80fd-25ec3995e7e2.png) 93 | 那我们再来看看 API 是怎么用的: 94 | 95 | - `set.insert(2)` 表示往集合中插入数值 2,成功插入返回 true,如果 2 已经存在集合中返回 false 96 | - `set.remove(2)` 表示从集合中删除数值 2,成功删除返回 true,如果 2 不存在集合中返回 false 97 | 98 | 可以看到这两个方法的参数都是值,而在数组中,要在常数时间内找到一个元素,必须要知道它的下标。所以显然我们还需要一个结构来记录集合中的值和它所在的数组下标的关系,这样一系列 `值->下标` 的对应关系,你应该能想到用一个哈希表来记录。 99 | 100 | ![hashmap 2](https://user-images.githubusercontent.com/30331289/83971179-85571580-a90c-11ea-9d55-ac8084caa9f7.png) 101 | 102 | 数组中存放着真正的值,而哈希表中存放着每个值所对应的数组下标。 103 | 104 | 但是,还有一个问题,还记得删除操作么?我们是先把要删除的元素和最后的元素换了位置再删除,换了位置后,两个元素的下标也变了。 105 | 106 | ![remove-swap](https://user-images.githubusercontent.com/30331289/83971306-58573280-a90d-11ea-85a8-883195931186.png) 107 | 108 | 所以很显然的,删除某个元素后,我们的哈希表也需要更新。 109 | 110 | ![update-hashmap](https://user-images.githubusercontent.com/30331289/83971286-3b226400-a90d-11ea-9bc1-012dcb67f885.png) 111 | 112 | ## 代码 113 | 114 | ```js 115 | class RandomizedSet { 116 | constructor() { 117 | // store the actual values 118 | this.array = []; 119 | // store the value-> index mapping 120 | this.map = {}; 121 | } 122 | 123 | insert(val) { 124 | if (val in this.map) return false; 125 | this.array.push(val); 126 | this.map[val] = this._size() - 1; 127 | return true; 128 | } 129 | 130 | remove(val) { 131 | if (!(val in this.map)) return false; 132 | 133 | const index = this.map[val]; 134 | const lastIndex = this._size() - 1; 135 | if (index < lastIndex) { 136 | this._swap(index, lastIndex); 137 | this.map[this.array[index]] = index; 138 | } 139 | this.array.pop(); 140 | delete this.map[val]; 141 | return true; 142 | } 143 | 144 | getRandom() { 145 | const size = this._size(); 146 | if (size === 0) return false; 147 | let randomIndex = Math.floor(Math.random() * size); 148 | return this.array[randomIndex]; 149 | } 150 | 151 | _size() { 152 | return this.array.length; 153 | } 154 | 155 | _swap(a, b) { 156 | const temp = this.array[b]; 157 | this.array[b] = this.array[a]; 158 | this.array[a] = temp; 159 | } 160 | } 161 | ``` 162 | 163 | _Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/23#issuecomment-640231502_ 164 | 165 | **[参考题解](_Originally posted by @azl397985856 in https://github.com/leetcode-pp/91alg-1/issues/23#issuecomment-640155651_)** 166 | -------------------------------------------------------------------------------- /basic/array-stack-queue/ext-remove-k-digits.md: -------------------------------------------------------------------------------- 1 | # 402. 移掉 K 位数字 2 | 3 | https://leetcode-cn.com/problems/remove-k-digits/ 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。 9 | 10 | 注意: 11 | 12 | num 的长度小于 10002 且 ≥ k。 13 | num 不会包含任何前导零。 14 | 示例 1 : 15 | 16 | 输入: num = "1432219", k = 3 17 | 输出: "1219" 18 | 解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。 19 | 示例 2 : 20 | 21 | 输入: num = "10200", k = 1 22 | 输出: "200" 23 | 解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。 24 | 示例 3 : 25 | 26 | 输入: num = "10", k = 2 27 | 输出: "0" 28 | 解释: 从原数字移除所有的数字,剩余为空就是0。 29 | 30 | 来源:力扣(LeetCode) 31 | 链接:https://leetcode-cn.com/problems/remove-k-digits 32 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 33 | ``` 34 | 35 | ## 方法 1:单调栈 36 | 37 | ### 思路 38 | 39 | ### 复杂度分析 40 | 41 | - 时间复杂度:$O(N)$。 42 | - 空间复杂度:$O(N)$。 43 | 44 | ### 代码 45 | 46 | JavaScript Code 47 | 48 | ```js 49 | /** 50 | * @param {string} num 51 | * @param {number} k 52 | * @return {string} 53 | */ 54 | var removeKdigits = function (num, k) { 55 | const stack = []; 56 | 57 | for (let i = 0; i < num.length; i++) { 58 | const n = +num[i]; 59 | 60 | while (k > 0 && stack.length > 0 && stack[stack.length - 1] > n) { 61 | stack.pop(); 62 | // 记录扔掉了几个数字,扔够 k 个就不扔了 63 | k--; 64 | } 65 | 66 | stack.push(n); 67 | } 68 | 69 | // 如果没有扔够 k 个的话,继续扔 70 | while (k-- > 0) { 71 | stack.pop(); 72 | } 73 | 74 | return stack.join('').replace(/^0*/, '') || '0'; 75 | }; 76 | ``` 77 | 78 | Python Code 79 | 80 | ```py 81 | class Solution(object): 82 | def removeKdigits(self, num, k): 83 | """ 84 | :type num: str 85 | :type k: int 86 | :rtype: str 87 | """ 88 | stack = [] 89 | remain = len(num) - k 90 | 91 | for digit in num: 92 | while k and stack and stack[-1] > digit: 93 | stack.pop() 94 | k -= 1 95 | stack.append(digit) 96 | 97 | return ''.join(stack[:remain]).lstrip('0') or '0' 98 | ``` 99 | 100 | 更多题解可以访问:[https://github.com/suukii/91-days-algorithm](https://github.com/suukii/91-days-algorithm) 101 | -------------------------------------------------------------------------------- /basic/array-stack-queue/ext-sort-colors.md: -------------------------------------------------------------------------------- 1 | # 75.颜色分类 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 7 | 8 | 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 9 | 10 | 注意: 11 | 不能使用代码库中的排序函数来解决这道题。 12 | 13 | 示例: 14 | 15 | 输入: [2,0,2,1,1,0] 16 | 输出: [0,0,1,1,2,2] 17 | 进阶: 18 | 19 | 一个直观的解决方案是使用计数排序的两趟扫描算法。 20 | 首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 21 | 你能想出一个仅使用常数空间的一趟扫描算法吗? 22 | 23 | 来源:力扣(LeetCode) 24 | 链接:https://leetcode-cn.com/problems/sort-colors 25 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 26 | ``` 27 | 28 | ## 伪代码 29 | 30 | ``` 31 | 我们使用三个指针:left, mid, right (left <= mid <= right),数组元素可以分成四段,'[' 表示包括当前元素,'(' 表示不包括: 32 | 33 | - [0, left) 是 bottom 元素 34 | - [left, mid) 是 middle 元素 35 | - [mid, right) 是待分堆的元素 36 | - [right, end] 是 top 元素 37 | 38 | 遍历待分堆的元素,将它们归类: 39 | 40 | // 当 [mid, right) 这个区间没有元素时停止遍历 41 | while mid <= right: 42 | 43 | if array[mid] < middle: 44 | swap(mid, left) 45 | left++ // 维护 bottom 元素的边界 46 | mid++ // 换过来的是一个 middle 元素,不需要继续分类,所以下一次循环从 mid + 1 开始归类 47 | 48 | else if array[mid] > middle: 49 | swap(mid, right) 50 | right-- // 维护 top 元素的边界 51 | // 换过来的元素原本在 [mid, right) 区间中,是一个待分堆元素,所以下一次循环还是从 mid 开始归类 52 | 53 | else: 54 | mid++ 55 | // 当前是一个 middle 元素,不需要交换,只要将 mid 右移一步扩大 [left, mid) 这个区间就行 56 | 57 | ``` 58 | 59 | ## 图解 60 | 61 | ![75_0](https://user-images.githubusercontent.com/30331289/83470720-7d831580-a4b5-11ea-9ad0-96cf730f72af.png) 62 | 63 | ## 复杂度 64 | 65 | - Time:$O(n)$,最多遍历一次数组,所以时间复杂度$O(n)$。 66 | - Space: $O(1)$,直接在原数组上进行修改,没有用到额外空间,所以空间复杂度是 $O(1)$。 67 | 68 | ## 代码 69 | 70 | JavaScript Code 71 | 72 | ```js 73 | /** 74 | * @param {number[]} nums 75 | * @return {void} Do not return anything, modify nums in-place instead. 76 | */ 77 | var sortColors = function (nums) { 78 | const swap = (list, p1, p2) => 79 | ([list[p1], list[p2]] = [list[p2], list[p1]]); 80 | let red = 0, 81 | blue = nums.length - 1, 82 | p = 0; 83 | 84 | while (p <= blue) { 85 | switch (nums[p]) { 86 | case 0: 87 | swap(nums, red++, p); 88 | p++; 89 | break; 90 | case 1: 91 | p++; 92 | break; 93 | case 2: 94 | swap(nums, blue--, p); 95 | break; 96 | default: 97 | break; 98 | } 99 | } 100 | }; 101 | ``` 102 | 103 | Python Code 104 | 105 | ```py 106 | class Solution(object): 107 | def sortColors(self, nums): 108 | """ 109 | :type nums: List[int] 110 | :rtype: None Do not return anything, modify nums in-place instead. 111 | """ 112 | midKey = 1 113 | left, mid, right = 0, 0, len(nums) - 1 114 | while mid <= right: 115 | if nums[mid] < midKey: 116 | nums[mid], nums[left] = nums[left], nums[mid] 117 | mid += 1 118 | left += 1 119 | elif nums[mid] > midKey: 120 | nums[mid], nums[right] = nums[right], nums[mid] 121 | right -= 1 122 | else: 123 | mid += 1 124 | ``` 125 | 126 | _Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/15#issuecomment-637217774_ 127 | 128 | **[参考题解](_Originally posted by @azl397985856 in https://github.com/leetcode-pp/91alg-1/issues/15#issuecomment-637651551_)** 129 | -------------------------------------------------------------------------------- /basic/binary-tree/15.sum-root-to-leaf-numbers.md: -------------------------------------------------------------------------------- 1 | # 129.求根到叶子节点数字之和 2 | 3 | https://leetcode-cn.com/problems/sum-root-to-leaf-numbers 4 | 5 | - [129.求根到叶子节点数字之和](#129求根到叶子节点数字之和) 6 | - [题目描述](#题目描述) 7 | - [方法 1:递归](#方法-1递归) 8 | - [思路](#思路) 9 | - [复杂度](#复杂度) 10 | - [代码](#代码) 11 | - [方法 2:BFS](#方法-2bfs) 12 | - [思路](#思路-1) 13 | - [复杂度](#复杂度-1) 14 | - [代码](#代码-1) 15 | 16 | ## 题目描述 17 | 18 | ``` 19 | 给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。 20 | 21 | 例如,从根到叶子节点路径 1->2->3 代表数字 123。 22 | 23 | 计算从根到叶子节点生成的所有数字之和。 24 | 25 | 说明: 叶子节点是指没有子节点的节点。 26 | 27 | 示例 1: 28 | 29 | 输入: [1,2,3] 30 | 1 31 | / \ 32 | 2 3 33 | 输出: 25 34 | 解释: 35 | 从根到叶子节点路径 1->2 代表数字 12. 36 | 从根到叶子节点路径 1->3 代表数字 13. 37 | 因此,数字总和 = 12 + 13 = 25. 38 | 示例 2: 39 | 40 | 输入: [4,9,0,5,1] 41 | 4 42 | / \ 43 | 9 0 44 | / \ 45 | 5 1 46 | 输出: 1026 47 | 解释: 48 | 从根到叶子节点路径 4->9->5 代表数字 495. 49 | 从根到叶子节点路径 4->9->1 代表数字 491. 50 | 从根到叶子节点路径 4->0 代表数字 40. 51 | 因此,数字总和 = 495 + 491 + 40 = 1026. 52 | 来源:力扣(LeetCode) 53 | 链接:https://leetcode-cn.com/problems/sum-root-to-leaf-numbers 54 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 55 | ``` 56 | 57 | ## 方法 1:递归 58 | 59 | ### 思路 60 | 61 | lucifer 的[递归小技巧](https://github.com/leetcode-pp/91alg-1/issues/32#issuecomment-643620727)练习课堂。 62 | 63 | 1. 定义函数功能,先不用管其具体实现。 64 | 65 | 我们需要一个函数,给定一个二叉树的根节点,返回根节点到各个叶子节点连成的数字的和。假设我们已经有这个函数 `F`,那问题就转化为 `F(root)` 了。 66 | 67 | 唔...其实问题还要复杂一丢丢,因为我们得要遍历到叶子节点的时候才能确定连成的数字,而要知道这个数字是什么,还要知道这个叶子节点的父节点,以及它父节点的父节点,...,一直到根节点,也就是说我们需要在遍历开始的时候就用一个变量来记录走过的节点。那现在我们的问题就转化成了 `F(root, num)`。 68 | 69 | 2. 确定大问题和小问题的关系。 70 | 71 | 首先,遍历到当前节点的时候,我们还有一点额外的工作要做,就是把当前节点的值记录到 `num` 中,这一步可以通过数学计算 `num * 10 + root.val` 来完成,或者你用字符串来拼接也行。 72 | 73 | 再来看看 `F(root, num)` 和 `F(root.left, num)` 以及 `F(root.right, num)` 的关系,它们的关系就很单纯啦。 74 | 75 | `F(root, num) = F(root.left, num) + F(root.right, num)` 76 | 77 | 3. 补充递归终止条件 78 | 79 | 显然,当遍历到叶子节点的时候我们就可以停止了,然后把遍历中生成的数字 `num` 返回。另外一种情况是遍历的节点不存在,那就直接返回 0 即可。 80 | 81 | ### 复杂度 82 | 83 | - 时间复杂度: $O(N)$,N 为二叉树节点数。 84 | - 空间复杂度: $O(h)$,h 为二叉树高度。 85 | 86 | ### 代码 87 | 88 | JavaScript Code 89 | 90 | ```js 91 | /** 92 | * Definition for a binary tree node. 93 | * function TreeNode(val) { 94 | * this.val = val; 95 | * this.left = this.right = null; 96 | * } 97 | */ 98 | /** 99 | * @param {TreeNode} root 100 | * @return {number} 101 | */ 102 | var sumNumbers = function (root, num = 0) { 103 | if (!root) return 0; 104 | num = num * 10 + root.val; 105 | if (!root.left && !root.right) return num; 106 | return sumNumbers(root.left, num) + sumNumbers(root.right, num); 107 | }; 108 | ``` 109 | 110 | Python Code 111 | 112 | ```py 113 | # Definition for a binary tree node. 114 | # class TreeNode(object): 115 | # def __init__(self, x): 116 | # self.val = x 117 | # self.left = None 118 | # self.right = None 119 | 120 | class Solution(object): 121 | def sumNumbers(self, root, num=0): 122 | """ 123 | :type root: TreeNode 124 | :rtype: int 125 | """ 126 | if not root: return 0 127 | num = num * 10 + root.val 128 | if not root.left and not root.right: return num 129 | return self.sumNumbers(root.left, num) + self.sumNumbers(root.right, num) 130 | ``` 131 | 132 | ## 方法 2:BFS 133 | 134 | ### 思路 135 | 136 | - 层级遍历,将每一层节点的数字传给下一层,存在 `node.val` 中。 137 | - 如果当前节点没有子节点了,就把它的值加到结果中。 138 | 139 | ### 复杂度 140 | 141 | - 时间复杂度: $O(N)$,N 为二叉树节点数。 142 | - 空间复杂度: $O(q)$,q 为队列长度。最坏情况是满二叉树,此时 q 为 $2/N$,即为 $O(N)$,N 为二叉树节点数。 143 | 144 | ## 代码 145 | 146 | JavaScript Code 147 | 148 | ```js 149 | /** 150 | * Definition for a binary tree node. 151 | * function TreeNode(val) { 152 | * this.val = val; 153 | * this.left = this.right = null; 154 | * } 155 | */ 156 | /** 157 | * @param {TreeNode} root 158 | * @return {number} 159 | */ 160 | var sumNumbers = function (root) { 161 | if (!root) return 0; 162 | 163 | const queue = [root]; 164 | let sum = 0; 165 | while (queue.length) { 166 | let len = queue.length; 167 | 168 | while (len-- > 0) { 169 | const node = queue.shift(); 170 | 171 | if (node.left) { 172 | node.left.val += node.val * 10; 173 | queue.push(node.left); 174 | } 175 | 176 | if (node.right) { 177 | node.right.val += node.val * 10; 178 | queue.push(node.right); 179 | } 180 | 181 | if (!node.left && !node.right) sum += node.val; 182 | } 183 | } 184 | return sum; 185 | }; 186 | ``` 187 | -------------------------------------------------------------------------------- /basic/binary-tree/ext-binary-tree-paths.md: -------------------------------------------------------------------------------- 1 | # 257.二叉树的所有路径 2 | 3 | https://leetcode-cn.com/problems/binary-tree-paths/ 4 | 5 | - [257.二叉树的所有路径](#257二叉树的所有路径) 6 | - [题目描述](#题目描述) 7 | - [方法 1:DFS](#方法-1dfs) 8 | - [思路](#思路) 9 | - [复杂度](#复杂度) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 给定一个二叉树,返回所有从根节点到叶子节点的路径。 16 | 17 | 说明: 叶子节点是指没有子节点的节点。 18 | 19 | 示例: 20 | 21 | 输入: 22 | 23 | 1 24 | / \ 25 | 2 3 26 | \ 27 | 5 28 | 29 | 输出: ["1->2->5", "1->3"] 30 | 31 | 解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3 32 | 33 | 来源:力扣(LeetCode) 34 | 链接:https://leetcode-cn.com/problems/binary-tree-paths 35 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 36 | ``` 37 | 38 | ## 方法 1:DFS 39 | 40 | ### 思路 41 | 42 | **DFS** 43 | 44 | 以这个输入为例: 45 | 46 | ``` 47 | 1 48 | / \ 49 | 2 3 50 | \ 51 | 5 52 | ``` 53 | 54 | - 想象一下,有一个小精灵拿着篮子站在了根节点 `1`,它把 `1` 放进了篮子里,接着,它的面前出现了两条路 `2` 和 `3`。 55 | - 小精灵使用影分身复制了一个自己和一个篮子,一个分身往左走,一个分身往右走。 56 | - 往左走的小精灵继续把 `2` 放进篮子,然后再往右下走把 `5` 放进篮子,这时候篮子是 `[1,2,5]`,由于在 `5` 没法继续前进了,这个篮子就被保存起来了。 57 | - 再回来看一开始往右走的小精灵,它往右下走到 `3`,把 `3` 装进篮子,这时候篮子是 `[1,3]`,由于没有路了,这个篮子也被保存了起来。 58 | - 小精灵最终得到了两个篮子 `[1,2,5]` 和 `[1,3]`。 59 | 60 | **回溯** 61 | 62 | 如果用上回溯的话,我们可以想象成从始至终只有一个篮子。 63 | 64 | - 小精灵拿着篮子从 `1` 开始,此时篮子是 `[1]`, 65 | - 接着它先往左走,走到 `2`,篮子是 `[1,2]`, 66 | - 从 `2` 再往右走,走到 `5`,篮子是 `[1,2,5]`,到这里已经不能继续前进了,小精灵就把篮子里的东西复制一份保存起来,然后转身往回走, 67 | - 一边走一边把篮子里的东西往外扔,从 `5` 走回 `2`,此时篮子是 `[1,2]`,把 `5` 扔掉了, 68 | - 从 `2` 开始也没有其他还没走过的路了,所以回退到 `1`,此时篮子只剩 `[1]`。 69 | - 到了 `1` 这个位置,小精灵发现右边的路还没走,于是就往右边去捡东西了,这个过程跟之前往左走是一样的。 70 | 71 | ### 复杂度 72 | 73 | - 时间复杂度:$O(n)$,n 为二叉树的节点数。 74 | - 空间复杂度:$O(h)$,h 为二叉树的高度。 75 | 76 | ### 代码 77 | 78 | JavaScript Code 79 | 80 | ```js 81 | /** 82 | * Definition for a binary tree node. 83 | * function TreeNode(val) { 84 | * this.val = val; 85 | * this.left = this.right = null; 86 | * } 87 | */ 88 | /** 89 | * @param {TreeNode} root 90 | * @return {string[]} 91 | */ 92 | var binaryTreePaths = function (root) { 93 | if (!root) return []; 94 | 95 | const ans = []; 96 | dfs(root, []); 97 | return ans; 98 | 99 | // ******************************* 100 | function dfs(node, path) { 101 | if (!node) return; 102 | 103 | if (!node.left && !node.right) { 104 | ans.push([...path, node.val].join('->')); 105 | return; 106 | } 107 | 108 | dfs(node.left, [...path, node.val]); 109 | dfs(node.right, [...path, node.val]); 110 | } 111 | }; 112 | ``` 113 | 114 | JavaScript Code 115 | 116 | ```js 117 | /** 118 | * Definition for a binary tree node. 119 | * function TreeNode(val) { 120 | * this.val = val; 121 | * this.left = this.right = null; 122 | * } 123 | */ 124 | /** 125 | * @param {TreeNode} root 126 | * @return {string[]} 127 | */ 128 | var binaryTreePaths = function (root) { 129 | if (!root) return []; 130 | 131 | const ans = []; 132 | const path = []; 133 | dfs(root); 134 | return ans; 135 | 136 | // ****************************** 137 | function dfs(node) { 138 | if (!node) return; 139 | 140 | path.push(node.val); 141 | 142 | if (!node.left && !node.right) { 143 | ans.push(path.join('->')); 144 | // 回溯 145 | path.pop(); 146 | return; 147 | } 148 | 149 | dfs(node.left); 150 | dfs(node.right); 151 | // 回溯 152 | path.pop(); 153 | } 154 | }; 155 | ``` 156 | -------------------------------------------------------------------------------- /basic/binary-tree/ext-construct-binary-tree-from-preorder-and-inorder-traversal.md: -------------------------------------------------------------------------------- 1 | # 105.从前序与中序遍历序列构造二叉树 2 | 3 | https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 根据一棵树的前序遍历与中序遍历构造二叉树。 9 | 10 | 注意: 11 | 你可以假设树中没有重复的元素。 12 | 13 | 例如,给出 14 | 15 | 前序遍历 preorder = [3,9,20,15,7] 16 | 中序遍历 inorder = [9,3,15,20,7] 17 | 返回如下的二叉树: 18 | 19 | 3 20 | / \ 21 | 9 20 22 | / \ 23 | 15 7 24 | 来源:力扣(LeetCode) 25 | 链接:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal 26 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 27 | ``` 28 | 29 | ## 思路 30 | 31 | 前序遍历的顺序是 `root->left->right`,也就是说在前序遍历的结果中,第一个节点就是 `root`,它的后边紧跟着左子树和右子树的前序遍历结果。 32 | 33 | 中序遍历的顺序是 `left->root->right`,也就是说在中序遍历的结果数组中,`root` 的左边是它左子树的中序遍历结果,它的右边是右子树的中序遍历结果。 34 | 35 | ![construct-binary-tree](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/construct_binary_tree.png) 36 | 37 | 结合以上两个知识点,我们可以从前序遍历结果中确定一棵二叉树的根节点,然后在中序遍历结果中找到根节点的位置(因为题目说了节点值是不重复的),从而确定了左子树和右子树遍历结果的长度,也就能分别得到左子树和右子树的前/中序遍历结果。 38 | 39 | 好了,现在我们来看看怎么用产品经理法来解决这个问题。 40 | 41 | **第一步**,我们的需求是要有一个 `F(preorder, inorder)` 函数, 输入一颗二叉树的前序遍历结果和中序遍历结果,返回一棵二叉树。 42 | 43 | **第二步**,确定大问题和小问题的关系。我们的大问题是 `F(preorder, inorder)`,小问题也是显而易见的 `F(leftPreorder, leftInorder)` 和 `F(rightPreorder, rightInorder)`。至于关系,`F(preorder, inorder)` 返回的是二叉树的根节点 `root`,两个小问题分别返回的是左子树和右子树,那么只需要将 `root` 的 `left` 和 `right` 指针分别指向他们的返回值即可。 44 | 45 | **第三步**,找到递归出口。一个出口是当遍历结果为空的时候,返回 `null`;另一个出口是当遍历结果长度为 1 的时候,也就是说这是个叶子节点,那我们就新建一个节点返回。 46 | 47 | ## 伪代码 48 | 49 | ``` 50 | buildTree(preorder, inorder): 51 | if isEmpty(preorder): return null 52 | if len(preorder) == 1: return TreeNode(preorder[0]) 53 | 54 | root = TreeNode(preorder[0]) 55 | root.left = buildTree(preorderOfLeft, inorderOfLeft) 56 | root.right = buildTree(preorderOfRight, inorderOfRight) 57 | return root 58 | ``` 59 | 60 | ## 复杂度分析 61 | 62 | - 时间复杂度:O(N),N 为节点数(另外每次在中序遍历结果中查找根节点的时间复杂度不会算 🥺) 63 | - 空间复杂度:O(N),返回的二叉树空间复杂度是 O(N),递归中调用栈的空间复杂度是 O(h),h 为树的高度,所以总的空间复杂度还是 O(N)。 64 | 65 | ## 代码 66 | 67 | Python Code 68 | 69 | ```py 70 | # Definition for a binary tree node. 71 | # class TreeNode(object): 72 | # def __init__(self, x): 73 | # self.val = x 74 | # self.left = None 75 | # self.right = None 76 | 77 | class Solution(object): 78 | def buildTree(self, preorder, inorder): 79 | """ 80 | :type preorder: List[int] 81 | :type inorder: List[int] 82 | :rtype: TreeNode 83 | """ 84 | if not len(preorder): return None 85 | if len(preorder) == 1: return TreeNode(preorder[0]) 86 | root = preorder[0] 87 | rootIndex = inorder.index(root) 88 | node = TreeNode(root) 89 | node.left = self.buildTree(preorder[1:rootIndex+1], inorder[0:rootIndex]) 90 | node.right = self.buildTree(preorder[rootIndex+1:], inorder[rootIndex+1:]) 91 | return node 92 | ``` 93 | 94 | JavaScript Code 95 | 96 | ```js 97 | /** 98 | * Definition for a binary tree node. 99 | * function TreeNode(val) { 100 | * this.val = val; 101 | * this.left = this.right = null; 102 | * } 103 | */ 104 | /** 105 | * @param {number[]} preorder 106 | * @param {number[]} inorder 107 | * @return {TreeNode} 108 | */ 109 | var buildTree = function (preorder, inorder) { 110 | if (preorder.length === 0) return null; 111 | if (preorder.length === 1) { 112 | return new TreeNode(preorder[0]); 113 | } 114 | const root = preorder[0]; 115 | const rootIndex = inorder.indexOf(root); 116 | 117 | const leftPreorder = preorder.slice(1, rootIndex + 1); 118 | const rightPreorder = preorder.slice(rootIndex + 1); 119 | const leftInorder = inorder.slice(0, rootIndex); 120 | const rightInorder = inorder.slice(rootIndex + 1); 121 | 122 | const rootNode = new TreeNode(root); 123 | rootNode.left = buildTree(leftPreorder, leftInorder); 124 | rootNode.right = buildTree(rightPreorder, rightInorder); 125 | return rootNode; 126 | }; 127 | ``` 128 | -------------------------------------------------------------------------------- /basic/binary-tree/ext-path-sum.md: -------------------------------------------------------------------------------- 1 | # 112.路径总和 2 | 3 | https://leetcode-cn.com/problems/path-sum/ 4 | 5 | - [112.路径总和](#112路径总和) 6 | - [题目描述](#题目描述) 7 | - [方法 1:递归](#方法-1递归) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | - [迭代+队列](#迭代队列) 12 | - [思路](#思路-1) 13 | - [复杂度分析](#复杂度分析-1) 14 | - [代码](#代码-1) 15 | 16 | ## 题目描述 17 | 18 | ``` 19 | 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 20 | 21 | 说明: 叶子节点是指没有子节点的节点。 22 | 23 | 示例:  24 | 给定如下二叉树,以及目标和 sum = 22, 25 | 26 | 5 27 | / \ 28 | 4 8 29 | / / \ 30 | 11 13 4 31 | / \ \ 32 | 7 2 1 33 | 返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。 34 | 35 | 来源:力扣(LeetCode) 36 | 链接:https://leetcode-cn.com/problems/path-sum 37 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 38 | ``` 39 | 40 | ## 方法 1:递归 41 | 42 | ### 思路 43 | 44 | **大问题** 45 | 46 | `F(root, sum)` 47 | 48 | **小问题** 49 | 50 | `F(root.left, sum - root.val)` 和 `F(root.right, sum - root.val)` 51 | 52 | **大问题和小问题的关系** 53 | 54 | `F(root, sum) = F(root.left, sum - root.val) or F(root.right, sum - root.val)` 55 | 56 | **递归出口** 57 | 58 | 1. 空节点:返回 false 59 | 2. 到达叶子节点: 60 | - `leaf.val == sum`: 返回 true 61 | - `leaf.val != sum`: 返回 false 62 | 63 | > 注意节点的值和 sum 都可能是负数。 64 | 65 | ### 复杂度分析 66 | 67 | - 时间复杂度:$O(N)$,N 为二叉树的节点总数。 68 | - 空间复杂度:$O(H)$,H 为二叉树的高度。 69 | 70 | ### 代码 71 | 72 | TypeScript Code 73 | 74 | ```ts 75 | /** 76 | * Definition for a binary tree node. 77 | * class TreeNode { 78 | * val: number 79 | * left: TreeNode | null 80 | * right: TreeNode | null 81 | * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { 82 | * this.val = (val===undefined ? 0 : val) 83 | * this.left = (left===undefined ? null : left) 84 | * this.right = (right===undefined ? null : right) 85 | * } 86 | * } 87 | */ 88 | 89 | function hasPathSum(root: TreeNode | null, sum: number): boolean { 90 | if (!root) return false; 91 | if (!root.left && !root.right) return root.val === sum; 92 | return ( 93 | hasPathSum(root.left, sum - root.val) || 94 | hasPathSum(root.right, sum - root.val) 95 | ); 96 | } 97 | ``` 98 | 99 | ## 迭代+队列 100 | 101 | ### 思路 102 | 103 | 使用迭代+队列的方式对二叉树进行 BFS,用另一个队列同时记录根节点到当前层次节点的路径和。 104 | 105 | ### 复杂度分析 106 | 107 | - 时间复杂度:$O(N)$,N 为二叉树的节点数。 108 | - 空间复杂度:$O(N)$,N 为二叉树的节点数。空间复杂度主要是队列的开销,队列中的元素个数最多不会超过二叉树的节点数。 109 | 110 | ### 代码 111 | 112 | TypeScript Code 113 | 114 | ```ts 115 | /** 116 | * Definition for a binary tree node. 117 | * class TreeNode { 118 | * val: number 119 | * left: TreeNode | null 120 | * right: TreeNode | null 121 | * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { 122 | * this.val = (val===undefined ? 0 : val) 123 | * this.left = (left===undefined ? null : left) 124 | * this.right = (right===undefined ? null : right) 125 | * } 126 | * } 127 | */ 128 | 129 | function hasPathSum(root: TreeNode | null, sum: number): boolean { 130 | if (!root) return false; 131 | 132 | const queue: TreeNode[] = [root]; 133 | const sums: number[] = [root.val]; 134 | 135 | while (true) { 136 | let len: number = queue.length; 137 | if (len === 0) break; 138 | while (len > 0) { 139 | len--; 140 | const node: TreeNode = queue.shift() as TreeNode; 141 | const temp: number = sums.shift() as number; 142 | 143 | if (!node.left && !node.right) { 144 | if (temp === sum) return true; 145 | continue; 146 | } 147 | if (node.left) { 148 | queue.push(node.left); 149 | sums.push(node.left.val + temp); 150 | } 151 | if (node.right) { 152 | queue.push(node.right); 153 | sums.push(node.right.val + temp); 154 | } 155 | } 156 | } 157 | return false; 158 | } 159 | ``` 160 | -------------------------------------------------------------------------------- /basic/binary-tree/ext-sum-of-left-leaves.md: -------------------------------------------------------------------------------- 1 | # 404.左叶子之和 2 | 3 | https://leetcode-cn.com/problems/sum-of-left-leaves 4 | 5 | - [404.左叶子之和](#404左叶子之和) 6 | - [题目描述](#题目描述) 7 | - [方法 1:递归](#方法-1递归) 8 | - [思路](#思路) 9 | - [复杂度](#复杂度) 10 | - [代码](#代码) 11 | - [方法 2:迭代+栈](#方法-2迭代栈) 12 | - [思路](#思路-1) 13 | - [复杂度](#复杂度-1) 14 | - [代码](#代码-1) 15 | 16 | ### 题目描述 17 | 18 | ``` 19 | 计算给定二叉树的所有左叶子之和。 20 | 21 | 示例: 22 | 23 | 3 24 | / \ 25 | 9 20 26 | / \ 27 | 15 7 28 | 29 | 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24 30 | 31 | 来源:力扣(LeetCode) 32 | 链接:https://leetcode-cn.com/problems/sum-of-left-leaves 33 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 34 | ``` 35 | 36 | ## 方法 1:递归 37 | 38 | ### 思路 39 | 40 | 根据题意,我们要找到所有 `左叶子节点`,并对它们的值求和。那怎样找到左叶子节点呢? 41 | 42 | 1. 首先,节点本身知道自己是不是叶子节点,只要判断 `!node.left && !node.right` 就好了,但它不知道自己是左/右子节点。 43 | 2. 然后,虽然节点自己不知道自己是左还是右子节点,但是它爸爸知道呀!所以,由它爸爸来告诉它就好了。 44 | 45 | ### 复杂度 46 | 47 | - 时间复杂度:$O(N)$,N 为节点数。 48 | - 空间复杂度:$O(h)$,h 为树的高度。 49 | 50 | ### 代码 51 | 52 | Python Code 53 | 54 | ```py 55 | # Definition for a binary tree node. 56 | # class TreeNode(object): 57 | # def __init__(self, x): 58 | # self.val = x 59 | # self.left = None 60 | # self.right = None 61 | 62 | class Solution(object): 63 | def sumOfLeftLeaves(self, root, left = False): 64 | """ 65 | :type root: TreeNode 66 | :rtype: int 67 | """ 68 | if not root: return 0 69 | if not root.left and not root.right: return root.val if left else 0 70 | return self.sumOfLeftLeaves(root.left, True) + self.sumOfLeftLeaves(root.right, False) 71 | ``` 72 | 73 | JavaScript Code 74 | 75 | ```js 76 | /** 77 | * Definition for a binary tree node. 78 | * function TreeNode(val) { 79 | * this.val = val; 80 | * this.left = this.right = null; 81 | * } 82 | */ 83 | /** 84 | * @param {TreeNode} root 85 | * @return {number} 86 | */ 87 | var sumOfLeftLeaves = function (root, isLeft = false) { 88 | if (!root) return 0; 89 | 90 | if (!root.left && !root.right && isLeft) return root.val; 91 | 92 | return ( 93 | sumOfLeftLeaves(root.left, true) + sumOfLeftLeaves(root.right, false) 94 | ); 95 | }; 96 | ``` 97 | 98 | ## 方法 2:迭代+栈 99 | 100 | ### 思路 101 | 102 | 其实只要知道怎么判断左叶子节点,剩下的就是遍历二叉树的模板了,爱怎么遍历都行。 103 | 104 | ### 复杂度 105 | 106 | - 时间复杂度:$O(N)$,N 为节点数。 107 | - 空间复杂度:$O(h)$,h 为树的高度。 108 | 109 | ### 代码 110 | 111 | JavaScript Code 112 | 113 | ```js 114 | /** 115 | * Definition for a binary tree node. 116 | * function TreeNode(val) { 117 | * this.val = val; 118 | * this.left = this.right = null; 119 | * } 120 | */ 121 | /** 122 | * @param {TreeNode} root 123 | * @return {number} 124 | */ 125 | var sumOfLeftLeaves = function (root) { 126 | if (!root) return 0; 127 | 128 | let sum = 0; 129 | const stack = [root]; 130 | 131 | while (stack.length) { 132 | const node = stack.pop(); 133 | 134 | if (!node.left && !node.right && node.isLeft) sum += node.val; 135 | delete node.isLeft; 136 | 137 | if (node.left) { 138 | node.left.isLeft = true; 139 | stack.push(node.left); 140 | } 141 | 142 | node.right && stack.push(node.right); 143 | } 144 | 145 | return sum; 146 | }; 147 | ``` 148 | -------------------------------------------------------------------------------- /basic/hashmap/19.two-sum.md: -------------------------------------------------------------------------------- 1 | # 1. 两数之和 2 | 3 | https://leetcode-cn.com/problems/two-sum 4 | 5 | - [1. 两数之和](#1-两数之和) 6 | - [题目描述](#题目描述) 7 | - [方法 1:哈希表](#方法-1哈希表) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | - [方法 2:排序+双指针](#方法-2排序双指针) 12 | - [思路](#思路-1) 13 | - [复杂度分析](#复杂度分析-1) 14 | - [代码](#代码-1) 15 | - [方法 3:暴力法](#方法-3暴力法) 16 | - [思路](#思路-2) 17 | - [复杂度分析](#复杂度分析-2) 18 | - [代码](#代码-2) 19 | 20 | ## 题目描述 21 | 22 | ``` 23 | 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 24 | 25 | 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 26 | 27 | 28 | 29 | 示例: 30 | 31 | 给定 nums = [2, 7, 11, 15], target = 9 32 | 33 | 因为 nums[0] + nums[1] = 2 + 7 = 9 34 | 所以返回 [0, 1] 35 | 36 | 来源:力扣(LeetCode) 37 | 链接:https://leetcode-cn.com/problems/two-sum 38 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 39 | ``` 40 | 41 | ## 方法 1:哈希表 42 | 43 | ### 思路 44 | 45 | 在遍历数组的同时把数字和下标存进哈希表中,然后看 `目标值 - 当前数字` 是否存在于哈希表中,有的话直接返回结果,没有就继续遍历。 46 | 47 | 由于不能使用同一个元素,要注意先检查,再将当前数字加入哈希表。 48 | 49 | ### 复杂度分析 50 | 51 | - 时间复杂度:$O(N)$,N 为数组长度,最坏的情况下数组中的每个元素都被访问一次,访问数组元素的时间是 $O(1)$,哈希表插入和查询元素的时间也是 $O(1)$。 52 | - 空间复杂度:$O(N)$,N 为数组长度,用来存元素和下标的哈希表所需的空间。 53 | 54 | ### 代码 55 | 56 | JavaScript Code 57 | 58 | ```js 59 | /** 60 | * @param {number[]} nums 61 | * @param {number} target 62 | * @return {number[]} 63 | */ 64 | var twoSum = function (nums, target) { 65 | if (!nums || !nums.length) return []; 66 | 67 | const map = {}; 68 | for (let i = 0; i < nums.length; i++) { 69 | const num = nums[i]; 70 | const diff = target - num; 71 | 72 | if (diff in map) return [map[diff], i]; 73 | map[num] = i; 74 | } 75 | }; 76 | ``` 77 | 78 | Python Code 79 | 80 | ```py 81 | class Solution(object): 82 | def twoSum(self, nums, target): 83 | """ 84 | :type nums: List[int] 85 | :type target: int 86 | :rtype: List[int] 87 | """ 88 | hashmap = {} 89 | for i, n in enumerate(nums): 90 | diff = target - n 91 | if diff in hashmap: 92 | return [hashmap[diff], i] 93 | hashmap[n] = i 94 | ``` 95 | 96 | ## 方法 2:排序+双指针 97 | 98 | ### 思路 99 | 100 | - 先给数组排序,再用双指针查找。 101 | - 不过题目要求返回下标,所以排序之前还需要保存原本的下标。 102 | 103 | ### 复杂度分析 104 | 105 | - 时间复杂度:$O(NlogN)$,N 为数组长度。 106 | - 空间复杂度:$O(1)$。(不是很确定,是 $O(1)$ 还是 $O(N)$) 107 | 108 | ### 代码 109 | 110 | JavaScript Code 111 | 112 | ```js 113 | /** 114 | * @param {number[]} nums 115 | * @param {number} target 116 | * @return {number[]} 117 | */ 118 | var twoSum = function (nums, target) { 119 | if (!nums || !nums.length) return []; 120 | 121 | // 保存原来的下标,然后升序排序,nums 中元素的格式是 [num, index] 122 | // [2,7,11,15] => [[2, 0], [7, 1], [11, 2], [15, 3]] 123 | nums = nums.map((n, i) => [n, i]); 124 | nums.sort((a, b) => a[0] - b[0]); 125 | 126 | let l = 0, 127 | r = nums.length - 1; 128 | while (l < r) { 129 | const n1 = nums[l][0], 130 | n2 = nums[r][0]; 131 | 132 | if (n1 + n2 === target) return [nums[l][1], nums[r][1]]; 133 | 134 | // 太大了,右指针左移 135 | if (n1 + n2 > target) r--; 136 | // 太小了,左指针右移 137 | else l++; 138 | } 139 | }; 140 | ``` 141 | 142 | ## 方法 3:暴力法 143 | 144 | ### 思路 145 | 146 | 暴力法也顺便做一下吧。 147 | 148 | ### 复杂度分析 149 | 150 | - 时间复杂度:$O(N^2)$,N 为数组长度。 151 | - 空间复杂度:$O(1)$。 152 | 153 | ### 代码 154 | 155 | JavaScript Code 156 | 157 | ```js 158 | /** 159 | * @param {number[]} nums 160 | * @param {number} target 161 | * @return {number[]} 162 | */ 163 | var twoSum = function (nums, target) { 164 | if (!nums || !nums.length) return []; 165 | 166 | for (let i = 0; i < nums.length; i++) { 167 | for (let j = i + 1; j < nums.length; j++) { 168 | if (nums[i] + nums[j] === target) return [i, j]; 169 | } 170 | } 171 | }; 172 | ``` 173 | -------------------------------------------------------------------------------- /basic/hashmap/21.number-of-boomerangs.md: -------------------------------------------------------------------------------- 1 | # 447.回旋镖的数量 2 | 3 | https://leetcode-cn.com/problems/number-of-boomerangs 4 | 5 | - [447.回旋镖的数量](#447回旋镖的数量) 6 | - [题目描述](#题目描述) 7 | - [方法 1:哈希表](#方法-1哈希表) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | - [输入输出](#输入输出) 12 | - [方法 2:暴力法](#方法-2暴力法) 13 | - [思路](#思路-1) 14 | - [复杂度](#复杂度) 15 | - [代码](#代码-1) 16 | 17 | ## 题目描述 18 | 19 | ``` 20 | 给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。 21 | 22 | 找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。 23 | 24 | 示例: 25 | 26 | 输入: 27 | [[0,0],[1,0],[2,0]] 28 | 29 | 输出: 30 | 2 31 | 32 | 解释: 33 | 两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]] 34 | 35 | 来源:力扣(LeetCode) 36 | 链接:https://leetcode-cn.com/problems/number-of-boomerangs 37 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 38 | ``` 39 | 40 | ## 方法 1:哈希表 41 | 42 | ### 思路 43 | 44 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/447_0.png) 45 | 46 | 根据题意,回旋镖是指,坐标上有三个点 i, j, k,i 到 j 的距离等于 i 到 k 的距离。上图中,蓝色的点到两个橙色的点距离是一样的,所以 (i, j, k) 构成了一个回旋镖,然后其实 (i, k, j) 也构成了一个回旋镖。 47 | 48 | 其实可以想到,对于每个点,我们只需要将其他点按照距离分组记录,比如上图中的 [1, 1],跟它距离 $\sqrt[]{2}$ 的点有 [0, 0] 和 [2, 0]。然后再计算排列组合数,也就是对 $\sqrt[]{2}$ 这个距离的分组中的点进行两两排列组合,就可以得出答案了。 49 | 50 | 如果用哈希表来存,它的结构大概是这样的: 51 | 52 | ``` 53 | { 54 | point1: { 55 | dist1: [point2, point3], 56 | dist2: [point4] 57 | }, 58 | point2: {}, 59 | } 60 | ``` 61 | 62 | 每个点都维护一个哈希表,哈希表的键是这个点到其他点的距离,值就是在这个距离的点有哪些。 63 | 64 | 不过题目只要求输出回旋镖的数量,所以我们只需要记录在某个距离的点有几个,并不需要记录具体的坐标,比如 `dist1: 2` 就可以了。 65 | 66 | **计算两点距离公式**: 67 | 68 | $\sqrt[2]{(x1-x2)^2+(y1-y2)^2}$ 69 | 70 | 不过其实我们只关心距离是否一样,并不关心实际距离是多少,所以实际上不需要开根号。 71 | 72 | **两两组合数**: 73 | 74 | 一个集合中有 N 个数,每个数都可以跟其余 N-1 个数进行组合,一共有 $N*(N-1)$ 种组合数。 75 | 76 | ### 复杂度分析 77 | 78 | - 时间复杂度:$O(N^2)$,N 是数组长度。在内层循环中,寻找该点到其他点的距离时间是 $O(N)$,计算组合数的时间是 $O(m)$,m 是每个点到其他点的不同距离总数的最大值,m 最大值是 N-1。所以总的时间是 $O(N*(N+m))$,差不多是 $O(N^2)$。 79 | - 空间复杂度:$O(N)$,最坏的情况是每个点到其他点的距离都不一样,那每个哈希表的大小就是 N-1。 80 | 81 | ### 代码 82 | 83 | JavaScript Code 84 | 85 | ```js 86 | /** 87 | * @param {number[][]} points 88 | * @return {number} 89 | */ 90 | var numberOfBoomerangs = function (points) { 91 | let count = 0; 92 | 93 | points.forEach((a, i) => { 94 | const map = {}; 95 | 96 | points.forEach((b, j) => { 97 | if (a !== b) { 98 | const dist = calcDistOf2Points(a, b); 99 | map[dist] = (map[dist] || 0) + 1; 100 | } 101 | }); 102 | 103 | for (const dist in map) { 104 | const num = map[dist]; 105 | if (num > 1) count += num * (num - 1); 106 | } 107 | }); 108 | 109 | return count; 110 | 111 | // ****************************** 112 | function calcDistOf2Points([x1, y1], [x2, y2]) { 113 | return (x1 - x2) ** 2 + (y1 - y2) ** 2; 114 | } 115 | }; 116 | ``` 117 | 118 | ### 输入输出 119 | 120 | Node.js 121 | 122 | ```js 123 | const __main__ = function () { 124 | const readline = require('readline'); 125 | const rl = readline.createInterface({ 126 | input: process.stdin, 127 | output: process.stdout, 128 | }); 129 | 130 | console.log('\n输入:\n'); 131 | rl.prompt(); 132 | const inputs = []; 133 | rl.on('line', line => inputs.push(line)); 134 | 135 | const outputs = []; 136 | rl.on('close', () => { 137 | inputs.forEach(line => { 138 | const output = numberOfBoomerangs(JSON.parse(line)); 139 | outputs.push(output); 140 | }); 141 | 142 | console.log('\n输出:\n'); 143 | outputs.forEach(el => console.log(`${el}\n`)); 144 | }); 145 | }; 146 | ``` 147 | 148 | ## 方法 2:暴力法 149 | 150 | ### 思路 151 | 152 | 三层循环吧,我就不写了。 153 | 154 | ### 复杂度 155 | 156 | - 时间复杂度:$O(N^3)$。 157 | - 空间复杂度:$O(1)$。 158 | 159 | ### 代码 160 | 161 | 略 162 | -------------------------------------------------------------------------------- /basic/hashmap/22.longest-substring-without-repeating-characters.md: -------------------------------------------------------------------------------- 1 | # 3. 无重复字符的最长子串 2 | 3 | https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/ 4 | 5 | - [3. 无重复字符的最长子串](#3-无重复字符的最长子串) 6 | - [题目描述](#题目描述) 7 | - [方法 1:滑动窗口+哈希表](#方法-1滑动窗口哈希表) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 16 | 17 | 示例 1: 18 | 19 | 输入: "abcabcbb" 20 | 输出: 3 21 | 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 22 | 示例 2: 23 | 24 | 输入: "bbbbb" 25 | 输出: 1 26 | 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 27 | 示例 3: 28 | 29 | 输入: "pwwkew" 30 | 输出: 3 31 | 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 32 |   请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 33 | 34 | 来源:力扣(LeetCode) 35 | 链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters 36 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 37 | ``` 38 | 39 | ## 方法 1:滑动窗口+哈希表 40 | 41 | ### 思路 42 | 43 | - 维护一个滑动窗口,当窗口中的字符不重复时,继续向右扩大窗口。 44 | - 当遇到重复字符 `d` 时,将窗口左侧收缩到 `d` 字符上次出现的位置 + 1。 45 | - 为了快速找到字符上次出现的位置,我们可以用一个哈希表来记录每个字符最新出现的位置。 46 | - 在滑动窗口遍历数组的过程中用一个变量记录窗口的最大长度。 47 | 48 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/3_0.png) 49 | 50 | ### 复杂度分析 51 | 52 | - 时间复杂度:$O(N)$,N 为 s 长度。 53 | - 空间复杂度:$O(d)$,d 是字符集的大小,但哈希表最大的大小也只是 $O(N)$。 54 | 55 | ### 代码 56 | 57 | JavaScript Code 58 | 59 | ```js 60 | /** 61 | * @param {string} s 62 | * @return {number} 63 | */ 64 | var lengthOfLongestSubstring = function (s) { 65 | const map = {}; 66 | let l = 0, 67 | r = 0, 68 | max = 0; 69 | 70 | while (r < s.length) { 71 | const pos = map[s[r]]; 72 | // 如果 s[r] 曾在 [l, r] 滑动窗口中出现 73 | // 就收缩滑动窗口左侧,把 l 指针移动到 s[r] 上次出现的位置 + 1 74 | if (pos >= l && pos <= r) l = pos + 1; 75 | 76 | // 更新 s[r] 出现的位置 77 | map[s[r]] = r; 78 | // 计算滑动窗口大小 79 | max = Math.max(max, r - l + 1); 80 | // 滑动窗口继续右移扩张 81 | r++; 82 | } 83 | return max; 84 | }; 85 | ``` 86 | -------------------------------------------------------------------------------- /basic/hashmap/23.substring-with-concatenation-of-all-words.md: -------------------------------------------------------------------------------- 1 | # 30. 串联所有单词的子串 2 | 3 | https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/ 4 | 5 | - [30. 串联所有单词的子串](#30-串联所有单词的子串) 6 | - [题目描述](#题目描述) 7 | - [方法 1: 哈希表](#方法-1-哈希表) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。 16 | 17 | 注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。 18 | 19 |   20 | 21 | 示例 1: 22 | 23 | 输入: 24 | s = "barfoothefoobarman", 25 | words = ["foo","bar"] 26 | 输出:[0,9] 27 | 解释: 28 | 从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。 29 | 输出的顺序不重要, [9,0] 也是有效答案。 30 | 示例 2: 31 | 32 | 输入: 33 | s = "wordgoodgoodgoodbestword", 34 | words = ["word","good","best","word"] 35 | 输出:[] 36 | 37 | 来源:力扣(LeetCode) 38 | 链接:https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words 39 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 40 | ``` 41 | 42 | ## 方法 1: 哈希表 43 | 44 | ### 思路 45 | 46 | naive 的想法是,将 words 里面的单词组合成字符串,然后到 s 里面去找有没有跟它一样的子串。但是,这样子时间复杂度太高了,因为 words 的组合有 `words.length!` 种,总复杂度差不多就是 `s.length * words.length!` 了。 47 | 48 | 那我们反过来想,s 的子串最多只有 `s.length` 个,那我们只要判断 s 的每个子串是不是刚好由 words 数组里面的所有单词组成的就可以了。所以说,如果子串是刚好匹配 words 的单词的话,说明子串和 words 组成的字符串是两种组合情况,虽然排序不同,但包含的单词种类和数量是一样的。 49 | 50 | 所以解决方法就很明显了: 51 | 52 | - 我们可以用哈希表来记录 words 里面有哪些单词,以及它们分别出现过几次(注意检查每个 s 子串时,哈希表都是新的)。 53 | - 然后开始遍历一个 s 的子串,将它截成一个个单词的长度,去哈希表中查询。 54 | - 如果出现在子串中的单词能在哈希表中找到,就将它的数量 -1,对消掉一个。 55 | - 如果这个单词在哈希表中找不到,或者数量已经变成了 0,就说明这个子串已经不符合我们的要求了,可以停止之后的比较了。 56 | - 如果子串的单词都比较完毕,然后哈希表中所有单词的数量也都刚好变成 0,就说明这个子串就是我们要找的。(这里到判断可以有多种,比如在开始子串比较之前用一个 count 变量来进行计数,如果子串比较结束后 count 刚好等于 words 的长度,就说明找到了符合要求的子串) 57 | - 接着换另一个子串,继续上述步骤。 58 | 59 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/30_0.png) 60 | 61 | ### 复杂度分析 62 | 63 | - 时间复杂度:$(n-k)*k$,n 是字符串 s 的长度,k 是单词的长度。 64 | - 空间复杂度:$m$,m 是 words 数组的长度,哈希表的空间。 65 | 66 | ### 代码 67 | 68 | JavaScript Code 69 | 70 | ```js 71 | /** 72 | * @param {string} s 73 | * @param {string[]} words 74 | * @return {number[]} 75 | */ 76 | var findSubstring = function (s, words) { 77 | const wordSize = words[0].length; 78 | const substringLen = wordSize * words.length; 79 | 80 | const wordsCount = {}; 81 | words.forEach(w => (wordsCount[w] = (wordsCount[w] || 0) + 1)); 82 | 83 | const res = []; 84 | for (let i = 0; i <= s.length - substringLen; i++) { 85 | const tempCount = { ...wordsCount }; 86 | let count = words.length; 87 | 88 | for (let j = i; j < i + substringLen; j += wordSize) { 89 | const word = s.slice(j, j + wordSize); 90 | 91 | if (!(word in tempCount) || tempCount[word] <= 0) break; 92 | 93 | tempCount[word]--; 94 | count--; 95 | } 96 | 97 | if (count === 0) res.push(i); 98 | } 99 | return res; 100 | }; 101 | ``` 102 | -------------------------------------------------------------------------------- /basic/hashmap/ext-kth-largest-element-in-an-array.md: -------------------------------------------------------------------------------- 1 | # 215. 数组中的第 K 个最大元素 2 | 3 | https://leetcode-cn.com/problems/kth-largest-element-in-an-array/ 4 | 5 | - [215. 数组中的第 K 个最大元素](#215-数组中的第-k-个最大元素) 6 | - [题目描述](#题目描述) 7 | - [方法 1:排序](#方法-1排序) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | - [方法 2:小顶堆](#方法-2小顶堆) 12 | - [思路](#思路-1) 13 | - [复杂度分析](#复杂度分析-1) 14 | - [代码](#代码-1) 15 | 16 | ## 题目描述 17 | 18 | ``` 19 | 在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 20 | 21 | 示例 1: 22 | 23 | 输入: [3,2,1,5,6,4] 和 k = 2 24 | 输出: 5 25 | 示例 2: 26 | 27 | 输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 28 | 输出: 4 29 | 说明: 30 | 31 | 你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。 32 | 33 | 来源:力扣(LeetCode) 34 | 链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array 35 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 36 | ``` 37 | 38 | ## 方法 1:排序 39 | 40 | ### 思路 41 | 42 | 直接给数组降序排序,再输出第 `k-1` 个数字。 43 | 44 | ### 复杂度分析 45 | 46 | - 时间复杂度:$O(NlogN)$,N 是数组长度。 47 | - 空间复杂度:$O(1)$。 48 | 49 | ### 代码 50 | 51 | JavaScript Code 52 | 53 | ```js 54 | /** 55 | * @param {number[]} nums 56 | * @param {number} k 57 | * @return {number} 58 | */ 59 | var findKthLargest = function (nums, k) { 60 | // 降序排序 61 | nums.sort((a, b) => b - a); 62 | return nums[k - 1]; 63 | }; 64 | ``` 65 | 66 | ## 方法 2:小顶堆 67 | 68 | ### 思路 69 | 70 | 维护一个大小为 k 的小顶堆,最后输出堆顶。 71 | 72 | 大顶堆也可以,就不写了。 73 | 74 | ### 复杂度分析 75 | 76 | - 时间复杂度:$O(klogk)$。 77 | - 空间复杂度:$O(k)$。 78 | 79 | ### 代码 80 | 81 | JavaScript Code 82 | 83 | ```js 84 | /** 85 | * @param {number[]} nums 86 | * @param {number} k 87 | * @return {number} 88 | */ 89 | var findKthLargest = function (nums, k) { 90 | const minHeap = new MinHeap(); 91 | 92 | nums.forEach(n => { 93 | const size = minHeap.size(); 94 | if (size < k) minHeap.insert(n); 95 | else if (size === k) { 96 | if (minHeap.peek() < n) { 97 | minHeap.pop(); 98 | minHeap.insert(n); 99 | } 100 | } 101 | }); 102 | return minHeap.peek(); 103 | }; 104 | 105 | // ************************************************* 106 | 107 | class Heap { 108 | constructor(list = [], comparator) { 109 | this.list = list; 110 | this.comparator = comparator; 111 | 112 | this.init(); 113 | } 114 | 115 | init() { 116 | const size = this.size(); 117 | for (let i = Math.floor(size / 2) - 1; i >= 0; i--) { 118 | this.heapify(this.list, size, i); 119 | } 120 | } 121 | 122 | insert(n) { 123 | this.list.push(n); 124 | const size = this.size(); 125 | for (let i = Math.floor(size / 2) - 1; i >= 0; i--) { 126 | this.heapify(this.list, size, i); 127 | } 128 | } 129 | 130 | peek() { 131 | return this.list[0]; 132 | } 133 | 134 | pop() { 135 | const last = this.list.pop(); 136 | if (this.size() === 0) return last; 137 | const returnItem = this.list[0]; 138 | this.list[0] = last; 139 | this.heapify(this.list, this.size(), 0); 140 | return returnItem; 141 | } 142 | 143 | size() { 144 | return this.list.length; 145 | } 146 | } 147 | 148 | class MinHeap extends Heap { 149 | constructor(list, comparator) { 150 | if (typeof comparator != 'function') { 151 | comparator = function comparator(inserted, compared) { 152 | return inserted > compared; 153 | }; 154 | } 155 | super(list, comparator); 156 | } 157 | 158 | heapify(arr, size, i) { 159 | let smallest = i; 160 | const left = Math.floor(i * 2 + 1); 161 | const right = Math.floor(i * 2 + 2); 162 | if (left < size && this.comparator(arr[smallest], arr[left])) 163 | smallest = left; 164 | if (right < size && this.comparator(arr[smallest], arr[right])) 165 | smallest = right; 166 | 167 | if (smallest !== i) { 168 | [arr[smallest], arr[i]] = [arr[i], arr[smallest]]; 169 | this.heapify(arr, size, smallest); 170 | } 171 | } 172 | } 173 | ``` 174 | -------------------------------------------------------------------------------- /basic/hashmap/ext-route-between-nodes-lcci.md: -------------------------------------------------------------------------------- 1 | # 面试题 04.01.节点间通路 2 | 3 | https://leetcode-cn.com/problems/route-between-nodes-lcci 4 | 5 | - [面试题 04.01.节点间通路](#面试题-0401节点间通路) 6 | - [题目描述](#题目描述) 7 | - [方法 1:图+DFS](#方法-1图dfs) 8 | - [思路](#思路) 9 | - [dfs 伪代码](#dfs-伪代码) 10 | - [代码](#代码) 11 | - [复杂度分析](#复杂度分析) 12 | 13 | ## 题目描述 14 | 15 | ``` 16 | 节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。 17 | 18 | 示例1: 19 | 20 | 输入:n = 3, graph = [[0, 1], [0, 2], [1, 2], [1, 2]], start = 0, target = 2 21 | 输出:true 22 | 示例2: 23 | 24 | 输入:n = 5, graph = [[0, 1], [0, 2], [0, 4], [0, 4], [0, 1], [1, 3], [1, 4], [1, 3], [2, 3], [3, 4]], start = 0, target = 4 25 | 输出 true 26 | 提示: 27 | 28 | 节点数量n在[0, 1e5]范围内。 29 | 节点编号大于等于 0 小于 n。 30 | 图中可能存在自环和平行边。 31 | 32 | 来源:力扣(LeetCode) 33 | 链接:https://leetcode-cn.com/problems/route-between-nodes-lcci 34 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 35 | ``` 36 | 37 | ## 方法 1:图+DFS 38 | 39 | ### 思路 40 | 41 | 简单学习了下图,[笔记](https://github.com/suukii/Articles/blob/master/articles/dsa/dsa_graph.md)。 42 | 43 | 1. 建一个邻接表 44 | 2. dfs 查找 45 | 46 | **邻接表** 47 | 48 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/0401_0.png) 49 | 50 | ### dfs 伪代码 51 | 52 | ``` 53 | 如果当前顶点就是目标顶点: 54 | return true 55 | 否则: 56 | 把当前顶点加入“已遍历”队列中 57 | let found = false 记录dfs邻接点是否能找到目标顶点 58 | 遍历当前顶点的所有邻接点: 59 | 如果这个邻接点是“未遍历”: 60 | 继续dfs查找,只要有一个查找返回了true,found = true 61 | return found 62 | ``` 63 | 64 | ### 代码 65 | 66 | JavaScript Code 67 | 68 | ```js 69 | /** 70 | * @param {number} n 71 | * @param {number[][]} graph 72 | * @param {number} start 73 | * @param {number} target 74 | * @return {boolean} 75 | */ 76 | var findWhetherExistsPath = function (n, graph, start, target) { 77 | // 建图 78 | const adjList = {}; 79 | for (let i = 0; i < n; i++) { 80 | adjList[i] = new Set(); 81 | } 82 | graph.forEach(edge => adjList[edge[0]].add(edge[1])); 83 | 84 | // dfs 85 | const dfs = (start, target, adjList, visited) => { 86 | if (start === target) return true; 87 | visited[start] = true; 88 | 89 | const neighs = adjList[start]; 90 | let found = false; 91 | neighs.forEach(neigh => { 92 | if (!visited[neigh]) { 93 | const res = dfs(neigh, target, adjList, visited); 94 | res && (found = res); 95 | } 96 | }); 97 | return found; 98 | }; 99 | 100 | return dfs(start, target, adjList, []); 101 | }; 102 | ``` 103 | 104 | ### 复杂度分析 105 | 106 | - 时间复杂度:$O(V+E)$,V 是顶点数,E 是边的数量。 107 | - 空间复杂度:$O(V+E)$,V 是顶点数,E 是边的数量,邻接表的空间复杂度是 $O(V+E)$,dfs 递归栈的空间复杂度是 $O(V)$。 108 | -------------------------------------------------------------------------------- /basic/hashmap/ext-set-mismatch.md: -------------------------------------------------------------------------------- 1 | # 645.错误的集合 2 | 3 | https://leetcode-cn.com/problems/set-mismatch 4 | 5 | - [645.错误的集合](#645错误的集合) 6 | - [题目描述](#题目描述) 7 | - [思路](#思路) 8 | - [复杂度分析](#复杂度分析) 9 | - [代码](#代码) 10 | - [输入输出](#输入输出) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 集合 S 包含从1到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个元素复制了成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有一个元素重复。 16 | 17 | 给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。 18 | 19 | 示例 1: 20 | 21 | 输入: nums = [1,2,2,4] 22 | 输出: [2,3] 23 | 注意: 24 | 25 | 给定数组的长度范围是 [2, 10000]。 26 | 给定的数组是无序的。 27 | 28 | 来源:力扣(LeetCode) 29 | 链接:https://leetcode-cn.com/problems/set-mismatch 30 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 31 | ``` 32 | 33 | ### 思路 34 | 35 | - 遍历数组,用哈希表记录出现过哪些数字,以及重复出现的数字是哪个。 36 | - 遍历数字 1-n,检查数字是否存在哈希表中,不存在则说明是缺失的数字。 37 | 38 | ### 复杂度分析 39 | 40 | - 时间复杂度:$O(N)$,N 为数组长度。 41 | - 空间复杂度:$O(N)$,N 为数组长度。 42 | 43 | ### 代码 44 | 45 | JavaScript Code 46 | 47 | ```js 48 | /** 49 | * @param {number[]} nums 50 | * @return {number[]} 51 | */ 52 | var findErrorNums = function (nums) { 53 | if (!nums || nums.length === 0) return []; 54 | 55 | const map = {}; 56 | let duplicate = 0; 57 | 58 | nums.forEach(n => { 59 | n in map && (duplicate = n); 60 | map[n] = true; 61 | }); 62 | 63 | for (let i = 1, n = nums.length; i <= n; i++) { 64 | if (i in map) continue; 65 | return [duplicate, i]; 66 | } 67 | }; 68 | ``` 69 | 70 | ### 输入输出 71 | 72 | Node 73 | 74 | ```js 75 | const readline = require('readline'); 76 | const rl = readline.createInterface({ 77 | input: process.stdin, 78 | output: process.stdout, 79 | }); 80 | 81 | console.log('\n输入:\n'); 82 | rl.prompt(); 83 | const inputs = []; 84 | rl.on('line', line => inputs.push(line)); 85 | 86 | const outputs = []; 87 | rl.on('close', () => { 88 | inputs.forEach(line => { 89 | const output = findErrorNums(JSON.parse(line)); 90 | outputs.push(output); 91 | }); 92 | 93 | console.log('\n输出:\n'); 94 | outputs.forEach(el => console.log(`${JSON.stringify(el)}`)); 95 | }); 96 | ``` 97 | -------------------------------------------------------------------------------- /basic/linked-list/07.swap-nodes-in-pairs.md: -------------------------------------------------------------------------------- 1 | # 24. 两两交换链表中的节点 2 | 3 | https://leetcode-cn.com/problems/swap-nodes-in-pairs/ 4 | 5 | - [24. 两两交换链表中的节点](#24dot-两两交换链表中的节点) 6 | - [题目描述](#题目描述) 7 | - [方法 1: 循环](#方法-1-循环) 8 | - [图解](#图解) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | - [方法 2: 递归](#方法-2-递归) 12 | - [思路](#思路) 13 | - [复杂度分析](#复杂度分析-1) 14 | - [代码](#代码-1) 15 | 16 | ## 题目描述 17 | 18 | ``` 19 | 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 20 | 21 | 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 22 | 23 | 24 | 示例 1: 25 | 26 | 27 | 输入:head = [1,2,3,4] 28 | 输出:[2,1,4,3] 29 | 示例 2: 30 | 31 | 输入:head = [] 32 | 输出:[] 33 | 示例 3: 34 | 35 | 输入:head = [1] 36 | 输出:[1] 37 |   38 | 39 | 提示: 40 | 41 | 链表中节点的数目在范围 [0, 100] 内 42 | 0 <= Node.val <= 100 43 | 44 | 来源:力扣(LeetCode) 45 | 链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs 46 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 47 | ``` 48 | 49 | ## 方法 1: 循环 50 | 51 | ### 图解 52 | 53 | - 要注意指针改变的顺序,不然可能会丢失节点。 54 | - 用一个 dummy 节点来简化操作,不用额外考虑链表头部的情况。 55 | 56 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/07.swap-nodes-in-pairs-02.png) 57 | 58 | ### 复杂度分析 59 | 60 | - 时间复杂度:$O(N)$, N 为链表长度。 61 | - 空间复杂度:$O(1)$。 62 | 63 | ### 代码 64 | 65 | JavaScript Code 66 | 67 | ```js 68 | /** 69 | * Definition for singly-linked list. 70 | * function ListNode(val, next) { 71 | * this.val = (val===undefined ? 0 : val) 72 | * this.next = (next===undefined ? null : next) 73 | * } 74 | */ 75 | /** 76 | * @param {ListNode} head 77 | * @return {ListNode} 78 | */ 79 | var swapPairs = function (head) { 80 | const dummy = new ListNode(null, head); 81 | let prev = dummy; 82 | let cur = prev.next; 83 | 84 | while (cur && cur.next) { 85 | // 按照上图,指针更换顺序是这样子的 86 | // prev.next = cur.next 87 | // cur.next = prev.next.next 88 | // prev.next.next = cur 89 | 90 | // 也可以先用一个指针把下一个节点存起来 91 | const next = cur.next; 92 | cur.next = next.next; 93 | next.next = cur; 94 | prev.next = next; 95 | 96 | prev = cur; 97 | cur = cur.next; 98 | } 99 | return dummy.next; 100 | }; 101 | ``` 102 | 103 | ## 方法 2: 递归 104 | 105 | ### 思路 106 | 107 | “将相邻的链表节点两两交换”,我们可以把链表两两分成若干组,在组内互换节点后再组合起来。 108 | 109 | 先解决小问题,再把小问题的解组合起来,解决大问题。 110 | 111 | **小问题** 112 | 113 | 只要关注当前递归要解决的问题就行,前后都可以当成是黑匣子。在当前递归中,我们只需要将两个节点互换。 114 | 115 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/07.swap-nodes-in-pairs-00.png) 116 | 117 | **小问题之间的关系** 118 | 119 | - 下一个递归应该返回互换后的第一个节点 120 | - 当前递归应该返回互换后的第一个节点给上一个递归 121 | 122 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/07.swap-nodes-in-pairs-01.png) 123 | 124 | **递归出口** 125 | 126 | - 链表尾部 `head === null` 127 | - 链表最后只剩下一个元素 `head.next === null` 128 | 129 | ### 复杂度分析 130 | 131 | - 时间复杂度:$O(N)$, N 为链表长度。 132 | - 空间复杂度:$O(N)$, N 为链表长度,递归栈的空间。 133 | 134 | ### 代码 135 | 136 | JavaScript Code 137 | 138 | ```js 139 | /** 140 | * Definition for singly-linked list. 141 | * function ListNode(val, next) { 142 | * this.val = (val===undefined ? 0 : val) 143 | * this.next = (next===undefined ? null : next) 144 | * } 145 | */ 146 | /** 147 | * @param {ListNode} head 148 | * @return {ListNode} 149 | */ 150 | var swapPairs = function (head) { 151 | // 递归出口 152 | if (!head || !head.next) return head; 153 | 154 | // 先保存下一个节点,避免丢失 155 | const next = head.next; 156 | 157 | // 下一个递归会返回互换后的第一个节点 158 | // head 是当前组互换后的第二个节点,head.next 指向下一组就好 159 | head.next = swapPairs(next.next); 160 | 161 | // 将当前组的两个节点互换 162 | next.next = head; 163 | 164 | // 返回互换后的第一个节点 165 | return next; 166 | }; 167 | ``` 168 | -------------------------------------------------------------------------------- /basic/linked-list/08.rotate-list.md: -------------------------------------------------------------------------------- 1 | # 61. 旋转链表 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。 7 | 8 | 示例 1: 9 | 10 | 输入: 1->2->3->4->5->NULL, k = 2 11 | 输出: 4->5->1->2->3->NULL 12 | 解释: 13 | 向右旋转 1 步: 5->1->2->3->4->NULL 14 | 向右旋转 2 步: 4->5->1->2->3->NULL 15 | 示例 2: 16 | 17 | 输入: 0->1->2->NULL, k = 4 18 | 输出: 2->0->1->NULL 19 | 解释: 20 | 向右旋转 1 步: 2->0->1->NULL 21 | 向右旋转 2 步: 1->2->0->NULL 22 | 向右旋转 3 步: 0->1->2->NULL 23 | 向右旋转 4 步: 2->0->1->NULL 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode-cn.com/problems/rotate-list 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | ``` 29 | 30 | ### 思路 31 | 32 | TODO 33 | 34 | 闭环,走 k 步,断开。 35 | 36 | ### 复杂度 37 | 38 | - 时间复杂度:$O(N)$, N 为链表长度。 39 | - 空间复杂度:$O(1)$。 40 | 41 | ### 代码 42 | 43 | JavaScript Code 44 | 45 | ```js 46 | /** 47 | * Definition for singly-linked list. 48 | * function ListNode(val) { 49 | * this.val = val; 50 | * this.next = null; 51 | * } 52 | */ 53 | /** 54 | * @param {ListNode} head 55 | * @param {number} k 56 | * @return {ListNode} 57 | */ 58 | var rotateRight = function (head, k) { 59 | if (!head || k === 0) return head; 60 | 61 | let cur = head, 62 | len = 1; 63 | // 成环 64 | while (cur.next) { 65 | cur = cur.next; 66 | len++; 67 | } 68 | cur.next = head; 69 | 70 | // 走 k 步 71 | cur = head; 72 | let n = len - (k % len) - 1; 73 | while (n > 0) { 74 | cur = cur.next; 75 | n--; 76 | } 77 | const newHead = cur.next; 78 | cur.next = null; 79 | return newHead; 80 | }; 81 | ``` 82 | -------------------------------------------------------------------------------- /basic/linked-list/09.convert-sorted-list-to-binary-search-tree.md: -------------------------------------------------------------------------------- 1 | # 109. 有序链表转换二叉搜索树 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。 7 | 8 | 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 9 | 10 | 示例: 11 | 12 | 给定的有序链表: [-10, -3, 0, 5, 9], 13 | 14 | 一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树: 15 | 16 | 0 17 | / \ 18 | -3 9 19 | / / 20 | -10 5 21 | 22 | 来源:力扣(LeetCode) 23 | 链接:https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree 24 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 25 | ``` 26 | 27 | ## 方法 1:递归 28 | 29 | ### 思路 30 | 31 | TODO 32 | 33 | - 先用快慢指针找到中间节点 34 | - 分治构建平衡二叉树 35 | 36 | ### 复杂度分析 37 | 38 | - 时间复杂度:$O(NlogN)$,N 为链表长度。 39 | - 空间复杂度:$O(logN)$,N 为链表长度。 40 | 41 | ### 代码 42 | 43 | JavaScript Code 44 | 45 | ```js 46 | /** 47 | * Definition for singly-linked list. 48 | * function ListNode(val, next) { 49 | * this.val = (val===undefined ? 0 : val) 50 | * this.next = (next===undefined ? null : next) 51 | * } 52 | */ 53 | /** 54 | * Definition for a binary tree node. 55 | * function TreeNode(val, left, right) { 56 | * this.val = (val===undefined ? 0 : val) 57 | * this.left = (left===undefined ? null : left) 58 | * this.right = (right===undefined ? null : right) 59 | * } 60 | */ 61 | /** 62 | * @param {ListNode} head 63 | * @return {TreeNode} 64 | */ 65 | var sortedListToBST = function (head, tail = null) { 66 | if (!head || head === tail) return null; 67 | 68 | let slow = head, 69 | fast = head; 70 | while (fast !== tail && fast.next !== tail) { 71 | slow = slow.next; 72 | fast = fast.next.next; 73 | } 74 | 75 | const root = new TreeNode(slow.val); 76 | root.left = sortedListToBST(head, slow); 77 | root.right = sortedListToBST(slow.next, tail); 78 | return root; 79 | }; 80 | ``` 81 | -------------------------------------------------------------------------------- /basic/linked-list/10.intersection-of-two-linked-lists.md: -------------------------------------------------------------------------------- 1 | # 160.相交链表 2 | 3 | https://leetcode-cn.com/problems/intersection-of-two-linked-lists 4 | 5 | - [160.相交链表](#160相交链表) 6 | - [题目描述](#题目描述) 7 | - [方法 1:暴力法](#方法-1暴力法) 8 | - [思路](#思路) 9 | - [复杂度](#复杂度) 10 | - [代码](#代码) 11 | - [方法 2:哈希表](#方法-2哈希表) 12 | - [思路](#思路-1) 13 | - [复杂度](#复杂度-1) 14 | - [代码](#代码-1) 15 | - [方法 3:双指针](#方法-3双指针) 16 | - [思路](#思路-2) 17 | - [复杂度](#复杂度-2) 18 | - [代码](#代码-2) 19 | 20 | ## 题目描述 21 | 22 | ``` 23 | 编写一个程序,找到两个单链表相交的起始节点。 24 | 25 | https://leetcode-cn.com/problems/intersection-of-two-linked-lists 26 | ``` 27 | 28 | ## 方法 1:暴力法 29 | 30 | ### 思路 31 | 32 | 对于链表 A 的每个节点,都去链表 B 中遍历一遍找看看有没有相同的节点。 33 | 34 | ### 复杂度 35 | 36 | - 时间复杂度:$O(M * N)$, M, N 分别为两个链表的长度。 37 | - 空间复杂度:$O(1)$。 38 | 39 | ### 代码 40 | 41 | JavaScript Code 42 | 43 | ```js 44 | /** 45 | * Definition for singly-linked list. 46 | * function ListNode(val) { 47 | * this.val = val; 48 | * this.next = null; 49 | * } 50 | */ 51 | 52 | /** 53 | * @param {ListNode} headA 54 | * @param {ListNode} headB 55 | * @return {ListNode} 56 | */ 57 | var getIntersectionNode = function (headA, headB) { 58 | if (!headA || !headB) return null; 59 | 60 | let pA = headA; 61 | while (pA) { 62 | let pB = headB; 63 | 64 | while (pB) { 65 | if (pA === pB) return pA; 66 | pB = pB.next; 67 | } 68 | 69 | pA = pA.next; 70 | } 71 | }; 72 | ``` 73 | 74 | ## 方法 2:哈希表 75 | 76 | ### 思路 77 | 78 | - 先遍历一遍链表 A,用哈希表把每个节点都记录下来(注意要存节点引用而不是节点值)。 79 | - 再去遍历链表 B,找到在哈希表中出现过的节点即为两个链表的交点。 80 | 81 | ### 复杂度 82 | 83 | - 时间复杂度:$O(M + N)$, M, N 分别为两个链表的长度。 84 | - 空间复杂度:$O(N)$,N 为链表 A 的长度。 85 | 86 | ### 代码 87 | 88 | JavaScript Code 89 | 90 | ```js 91 | /** 92 | * Definition for singly-linked list. 93 | * function ListNode(val) { 94 | * this.val = val; 95 | * this.next = null; 96 | * } 97 | */ 98 | 99 | /** 100 | * @param {ListNode} headA 101 | * @param {ListNode} headB 102 | * @return {ListNode} 103 | */ 104 | var getIntersectionNode = function (headA, headB) { 105 | if (!headA || !headB) return null; 106 | 107 | const hashmap = new Map(); 108 | 109 | let pA = headA; 110 | while (pA) { 111 | hashmap.set(pA, 1); 112 | pA = pA.next; 113 | } 114 | 115 | let pB = headB; 116 | while (pB) { 117 | if (hashmap.has(pB)) return pB; 118 | pB = pB.next; 119 | } 120 | }; 121 | ``` 122 | 123 | ## 方法 3:双指针 124 | 125 | ### 思路 126 | 127 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/intersection_of_linked_lists.png) 128 | 129 | **如果链表有交点** 130 | 131 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/intersection_of_linked_lists_1.png) 132 | 133 | **如果链表没有交点** 134 | 135 | 1. 两个链表长度一样,第一次遍历结束后 pA 和 pB 都是 null,结束遍历 136 | 2. 两个链表长度不一样,两次遍历结束后 pA 和 pB 都是 null,结束遍历 137 | 138 | ### 复杂度 139 | 140 | - 时间复杂度:$O(M + N)$, M, N 分别为两个链表的长度。 141 | - 空间复杂度:$O(1)$。 142 | 143 | ### 代码 144 | 145 | ```js 146 | /** 147 | * Definition for singly-linked list. 148 | * function ListNode(val) { 149 | * this.val = val; 150 | * this.next = null; 151 | * } 152 | */ 153 | 154 | /** 155 | * @param {ListNode} headA 156 | * @param {ListNode} headB 157 | * @return {ListNode} 158 | */ 159 | var getIntersectionNode = function (headA, headB) { 160 | if (!headA || !headB) return null; 161 | 162 | let pA = headA, 163 | pB = headB; 164 | while (pA !== pB) { 165 | pA = pA === null ? headB : pA.next; 166 | pB = pB === null ? headA : pB.next; 167 | } 168 | return pA; 169 | }; 170 | ``` 171 | -------------------------------------------------------------------------------- /basic/linked-list/ext-merge-two-sorted-lists.md: -------------------------------------------------------------------------------- 1 | # 21. 合并两个有序链表 2 | 3 | https://leetcode-cn.com/problems/merge-two-sorted-lists/ 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。  9 | 10 | 示例: 11 | 12 | 输入:1->2->4, 1->3->4 13 | 输出:1->1->2->3->4->4 14 | 15 | 来源:力扣(LeetCode) 16 | 链接:https://leetcode-cn.com/problems/merge-two-sorted-lists 17 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 18 | ``` 19 | 20 | ## 方法 1: 迭代 21 | 22 | ### 思路 23 | 24 | - 用两个指针分别遍历两个链表,比较两个指针节点的大小。 25 | - 将较小的那个节点加到新链表中,然后指针后移。 26 | - 较大的那个节点指针不动,下一轮继续比较。 27 | - 如果两个链表长度不一样,继续遍历将多出来的节点加到新链表中就好了。 28 | 29 | ### 复杂度分析 30 | 31 | - 时间复杂度:$O(N)$, N 为链表长度。 32 | - 空间复杂度:$O(1)$。 33 | 34 | ### 代码 35 | 36 | JavaScript Code 37 | 38 | ```js 39 | /** 40 | * Definition for singly-linked list. 41 | * function ListNode(val, next) { 42 | * this.val = (val===undefined ? 0 : val) 43 | * this.next = (next===undefined ? null : next) 44 | * } 45 | */ 46 | /** 47 | * @param {ListNode} l1 48 | * @param {ListNode} l2 49 | * @return {ListNode} 50 | */ 51 | var mergeTwoLists = function (l1, l2) { 52 | const dummy = new ListNode(); 53 | let p1 = l1, 54 | p2 = l2, 55 | cur = dummy; 56 | 57 | while (p1 || p2) { 58 | if (!p2 || (p1 && p1.val <= p2.val)) { 59 | cur.next = new ListNode(p1.val); 60 | p1 = p1.next; 61 | } else { 62 | cur.next = new ListNode(p2.val); 63 | p2 = p2.next; 64 | } 65 | cur = cur.next; 66 | } 67 | 68 | return dummy.next; 69 | }; 70 | ``` 71 | 72 | ## 方法 2: 递归 73 | 74 | ### 思路 75 | 76 | - 如果 `l1 < l2`,那就把 l1 作为合并链表的头部返回,然后继续合并 `l1.next` 和 `l2` 之后的节点。 77 | - 如果 `l2 < l1`,那就把 l2 作为合并链表的头部返回,然后继续合并 `l2.next` 和 `l1` 之后的节点。 78 | - 递归出口: 79 | - 没有节点了。 80 | - 只剩一个节点,不用比较,返回那个节点就行。 81 | 82 | ### 复杂度分析 83 | 84 | - 时间复杂度:$O(N)$, N 为链表长度。 85 | - 空间复杂度:$O(N)$, N 为链表长度,递归栈的空间。 86 | 87 | ### 代码 88 | 89 | JavaScript Code 90 | 91 | ```js 92 | /** 93 | * Definition for singly-linked list. 94 | * function ListNode(val, next) { 95 | * this.val = (val===undefined ? 0 : val) 96 | * this.next = (next===undefined ? null : next) 97 | * } 98 | */ 99 | /** 100 | * @param {ListNode} l1 101 | * @param {ListNode} l2 102 | * @return {ListNode} 103 | */ 104 | var mergeTwoLists = function (l1, l2) { 105 | if (!l1) return l2; 106 | if (!l2) return l1; 107 | 108 | if (l1.val < l2.val) { 109 | l1.next = mergeTwoLists(l1.next, l2); 110 | return l1; 111 | } else { 112 | l2.next = mergeTwoLists(l1, l2.next); 113 | return l2; 114 | } 115 | }; 116 | ``` 117 | -------------------------------------------------------------------------------- /basic/linked-list/ext-remove-duplicates-from-sorted-list.md: -------------------------------------------------------------------------------- 1 | # 83. 删除排序链表中的重复元素 2 | 3 | https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list 4 | 5 | - [83. 删除排序链表中的重复元素](#83-删除排序链表中的重复元素) 6 | - [题目描述](#题目描述) 7 | - [方法 1:迭代](#方法-1迭代) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 16 | 17 | 示例 1: 18 | 19 | 输入: 1->1->2 20 | 输出: 1->2 21 | 示例 2: 22 | 23 | 输入: 1->1->2->3->3 24 | 输出: 1->2->3 25 | 26 | 来源:力扣(LeetCode) 27 | 链接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list 28 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 29 | ``` 30 | 31 | ## 方法 1:迭代 32 | 33 | ### 思路 34 | 35 | 简单题,只是考察操作节点指针的能力,直接看代码吧。 36 | 37 | ### 复杂度分析 38 | 39 | - 时间复杂度:O$(N)$,N 为链表长度。 40 | - 空间复杂度:O$(1)$。 41 | 42 | ### 代码 43 | 44 | JavaScript Code 45 | 46 | ```js 47 | /** 48 | * Definition for singly-linked list. 49 | * function ListNode(val) { 50 | * this.val = val; 51 | * this.next = null; 52 | * } 53 | */ 54 | /** 55 | * @param {ListNode} head 56 | * @return {ListNode} 57 | */ 58 | var deleteDuplicates = function (head) { 59 | if (!head) return null; 60 | 61 | let cur = head; 62 | while (cur) { 63 | // 如果下一个节点与当前节点相同,删掉下一个节点 64 | while (cur.next && cur.next.val === cur.val) { 65 | const next = cur.next; 66 | cur.next = next.next; 67 | next.next = null; 68 | } 69 | // 前移一步 70 | cur = cur.next; 71 | } 72 | return head; 73 | }; 74 | ``` 75 | -------------------------------------------------------------------------------- /basic/linked-list/ext-reverse-linked-list.md: -------------------------------------------------------------------------------- 1 | # 206.反转链表 2 | 3 | https://leetcode-cn.com/problems/reverse-linked-list 4 | 5 | - [206.反转链表](#206反转链表) 6 | - [题目描述](#题目描述) 7 | - [循环](#循环) 8 | - [思路](#思路) 9 | - [图解](#图解) 10 | - [代码](#代码) 11 | - [递归](#递归) 12 | - [思路](#思路-1) 13 | - [图解](#图解-1) 14 | - [代码](#代码-1) 15 | 16 | ## 题目描述 17 | 18 | ``` 19 | 反转一个单链表。 20 | 21 | 示例: 22 | 23 | 输入: 1->2->3->4->5->NULL 24 | 输出: 5->4->3->2->1->NULL 25 | 进阶: 26 | 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 27 | 28 | 来源:力扣(LeetCode) 29 | 链接:https://leetcode-cn.com/problems/reverse-linked-list 30 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 31 | ``` 32 | 33 | ## 循环 34 | 35 | ### 思路 36 | 37 | 1. 初始化一个 `prev` 指针为 null,一个 `cur` 指针为 head; 38 | 2. 开始遍历链表,在每一次循环中: 39 | 40 | - 先保存 `cur.next`; 41 | - 把 `cur.next` 倒转方向指向 `prev`; 42 | - `prev` 和 `cur` 都分别往前一步; 43 | 44 | ### 图解 45 | 46 | ![reverse-a-linked-list-loop](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/reverse-a-linked-list-loop.png) 47 | 48 | ### 代码 49 | 50 | ```py 51 | # Definition for singly-linked list. 52 | # class ListNode(object): 53 | # def __init__(self, x): 54 | # self.val = x 55 | # self.next = None 56 | 57 | class Solution(object): 58 | def reverseList(self, head): 59 | """ 60 | :type head: ListNode 61 | :rtype: ListNode 62 | """ 63 | prev, cur = None, head 64 | while cur != None: 65 | nextNode = cur.next 66 | cur.next = prev 67 | prev = cur 68 | cur = nextNode 69 | return prev 70 | ``` 71 | 72 | ## 递归 73 | 74 | ### 思路 75 | 76 | 我们可以把链表分成两个部分: 77 | 78 | - 第一个节点 79 | - 余下的部分 80 | 81 | 假设余下的部分是已经反转好的链表,那我们就只需要把这部分的最后一个节点指向原本的第一个节点,然后返回余下部分的 head。 82 | 83 | 而余下的部分也可以进一步分成两个部分: 84 | 85 | - 第一个节点 86 | - 余下的部分 87 | 88 | ...... 89 | 90 | 这样我们应该就能看到一个递归的套路了,就是把一个大问题一步步地拆分成越来越小的小问题,然后从最小的问题开始一个个往上解决,等把所有小问题都解决了,原本的大问题也就解决了。 91 | 92 | 那关键点就在于我们得找到可以被直接解决的最小问题,也就是递归的出口。在这道题目中,很明显这个最小问题就是当链表被分成只剩下最后一个节点的时候,我们只需要直接返回当前节点作为 head。 93 | 94 | ### 图解 95 | 96 | ![reverse-a-linked-list-recursive](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/reverse-a-linked-list-recursive.png) 97 | 98 | ### 代码 99 | 100 | ```py 101 | # Definition for singly-linked list. 102 | # class ListNode(object): 103 | # def __init__(self, x): 104 | # self.val = x 105 | # self.next = None 106 | 107 | class Solution(object): 108 | def reverseList(self, head): 109 | """ 110 | :type head: ListNode 111 | :rtype: ListNode 112 | """ 113 | if head == None or head.next == None: return head 114 | newHead = self.reverseList(head.next) 115 | head.next.next = head 116 | head.next = None 117 | return newHead 118 | ``` 119 | -------------------------------------------------------------------------------- /basic/two-pointers/25.search-insert-position.md: -------------------------------------------------------------------------------- 1 | # 35. 搜索插入位置 2 | 3 | https://leetcode-cn.com/problems/search-insert-position/ 4 | 5 | - [35. 搜索插入位置](#35-搜索插入位置) 6 | - [题目描述](#题目描述) 7 | - [方法 1:二分查找](#方法-1二分查找) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 16 | 17 | 你可以假设数组中无重复元素。 18 | 19 | 示例 1: 20 | 21 | 输入: [1,3,5,6], 5 22 | 输出: 2 23 | 示例 2: 24 | 25 | 输入: [1,3,5,6], 2 26 | 输出: 1 27 | 示例 3: 28 | 29 | 输入: [1,3,5,6], 7 30 | 输出: 4 31 | 示例 4: 32 | 33 | 输入: [1,3,5,6], 0 34 | 输出: 0 35 | 36 | 来源:力扣(LeetCode) 37 | 链接:https://leetcode-cn.com/problems/search-insert-position 38 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 39 | ``` 40 | 41 | ## 方法 1:二分查找 42 | 43 | ### 思路 44 | 45 | 用二分法找到目标值,或者找到大于目标值的数字中最左边的那个,返回下标。 46 | 47 | ### 复杂度分析 48 | 49 | - 时间复杂度:$O(logN)$,N 是数组长度。 50 | - 空间复杂度:$O(1)$。 51 | 52 | ### 代码 53 | 54 | JavaScript Code 55 | 56 | ```js 57 | /** 58 | * @param {number[]} nums 59 | * @param {number} target 60 | * @return {number} 61 | */ 62 | var searchInsert = function (nums, target) { 63 | let l = 0, 64 | r = nums.length - 1, 65 | mid = 0; 66 | 67 | while (l <= r) { 68 | mid = ((r - l) / 2 + l) << 0; 69 | if (nums[mid] < target) l = mid + 1; 70 | else if (nums[mid] > target) r = mid - 1; 71 | else return mid; 72 | } 73 | 74 | return l; 75 | }; 76 | ``` 77 | -------------------------------------------------------------------------------- /basic/two-pointers/26.search-a-2d-matrix.md: -------------------------------------------------------------------------------- 1 | # 74. 搜索二维矩阵 2 | 3 | https://leetcode-cn.com/problems/search-a-2d-matrix 4 | 5 | - [74. 搜索二维矩阵](#74-搜索二维矩阵) 6 | - [题目描述](#题目描述) 7 | - [方法 1:二分法](#方法-1二分法) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性: 16 | 17 | 每行中的整数从左到右按升序排列。 18 | 每行的第一个整数大于前一行的最后一个整数。 19 |   20 | 21 | 示例 1: 22 | ``` 23 | 24 | ![](https://assets.leetcode.com/uploads/2020/10/05/mat.jpg) 25 | 26 | ``` 27 | 28 | 输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 3 29 | 输出:true 30 | 示例 2: 31 | ``` 32 | 33 | ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/25/mat2.jpg) 34 | 35 | ``` 36 | 输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 13 37 | 输出:false 38 | 示例 3: 39 | 40 | 输入:matrix = [], target = 0 41 | 输出:false 42 |   43 | 44 | 提示: 45 | 46 | m == matrix.length 47 | n == matrix[i].length 48 | 0 <= m, n <= 100 49 | -104 <= matrix[i][j], target <= 104 50 | 51 | 来源:力扣(LeetCode) 52 | 链接:https://leetcode-cn.com/problems/search-a-2d-matrix 53 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 54 | ``` 55 | 56 | ## 方法 1:二分法 57 | 58 | ### 思路 59 | 60 | 如果我们能把二维数组打平成一维数组,那就是套个二分法模板的事啦。 61 | 62 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/74_0.png) 63 | 64 | 剩下的问题是,怎么从一维数组的下标逆推二维数组的坐标?其实就是简单的数学计算。 65 | 66 | 我们将二维数组中的数字从左到右、从上到下,标记为第 `0~(m x n)` 个数字,用 `pos` 来表示。那么 `pos` 与数字在二维矩阵中坐标的关系如下: 67 | 68 | ``` 69 | x: floor(pos / cols) 70 | y: pos % cols 71 | ``` 72 | 73 | - pos: 第 n 个数字 74 | - cols: 二维矩阵的列数 75 | 76 | ### 复杂度分析 77 | 78 | - 时间复杂度:$O(log(m*n))$,$m * n$ 是矩阵的大小。 79 | - 空间复杂度:$O(1)$。 80 | 81 | ### 代码 82 | 83 | JavaScript Code 84 | 85 | ```js 86 | /** 87 | * @param {number[][]} matrix 88 | * @param {number} target 89 | * @return {boolean} 90 | */ 91 | var searchMatrix = function (matrix, target) { 92 | if (!matrix || !matrix.length) return false; 93 | 94 | const rows = matrix.length; 95 | const cols = matrix[0].length; 96 | let l = 0, 97 | r = rows * cols - 1, 98 | mid = 0; 99 | 100 | while (l <= r) { 101 | mid = ((l + r) / 2) << 0; 102 | const [x, y] = getCoordFromPos(mid); 103 | const num = matrix[x][y]; 104 | if (num < target) l = mid + 1; 105 | else if (num > target) r = mid - 1; 106 | else return true; 107 | } 108 | 109 | return false; 110 | 111 | // ************************************* 112 | function getCoordFromPos(pos) { 113 | const x = (pos / cols) << 0; 114 | const y = pos % cols; 115 | return [x, y]; 116 | } 117 | }; 118 | ``` 119 | -------------------------------------------------------------------------------- /basic/two-pointers/27.remove-duplicates-from-sorted-array.md: -------------------------------------------------------------------------------- 1 | # 26.删除排序数组中的重复项 2 | 3 | https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/ 4 | 5 | - [26.删除排序数组中的重复项](#26删除排序数组中的重复项) 6 | - [题目描述](#题目描述) 7 | - [双指针](#双指针) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 16 | 17 | 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 18 | 19 | 20 | 21 | 示例 1: 22 | 23 | 给定数组 nums = [1,1,2], 24 | 25 | 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 26 | 27 | 你不需要考虑数组中超出新长度后面的元素。 28 | 示例 2: 29 | 30 | 给定 nums = [0,0,1,1,1,2,2,3,3,4], 31 | 32 | 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 33 | 34 | 你不需要考虑数组中超出新长度后面的元素。 35 | 36 | 37 | 说明: 38 | 39 | 为什么返回数值是整数,但输出的答案是数组呢? 40 | 41 | 请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 42 | 43 | 你可以想象内部操作如下: 44 | 45 | // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 46 | int len = removeDuplicates(nums); 47 | 48 | // 在函数里修改输入数组对于调用者是可见的。 49 | // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 50 | for (int i = 0; i < len; i++) { 51 | print(nums[i]); 52 | } 53 | ``` 54 | 55 | ## 双指针 56 | 57 | ### 思路 58 | 59 | - 用一个读指针,一个写指针遍历数组。 60 | - 遇到重复的元素 `读指针` 就继续前移。 61 | - 遇到不同的元素 `写指针` 就前移一步,写入那个元素。 62 | 63 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/26_0.png) 64 | 65 | ### 复杂度分析 66 | 67 | - 时间复杂度:$O(N)$,N 为数组长度。 68 | - 空间复杂度:$O(1)$。 69 | 70 | ### 代码 71 | 72 | JavaScript Code 73 | 74 | ```js 75 | /** 76 | * @param {number[]} nums 77 | * @return {number} 78 | */ 79 | var removeDuplicates = function (nums) { 80 | let p1 = 0, 81 | p2 = 0; 82 | 83 | while (p2 < nums.length) { 84 | if (nums[p1] != nums[p2]) { 85 | p1++; 86 | nums[p1] = nums[p2]; 87 | } 88 | p2++; 89 | } 90 | return p1 + 1; 91 | }; 92 | ``` 93 | 94 | Python Code 95 | 96 | ```py 97 | class Solution(object): 98 | def removeDuplicates(self, nums): 99 | """ 100 | :type nums: List[int] 101 | :rtype: int 102 | """ 103 | if not nums: return 0 104 | 105 | l, r = 0, 0 106 | while r < len(nums): 107 | if nums[l] != nums[r]: 108 | l += 1 109 | nums[l] = nums[r] 110 | r += 1 111 | return l + 1 112 | ``` 113 | -------------------------------------------------------------------------------- /basic/two-pointers/28.middle-of-the-linked-list.md: -------------------------------------------------------------------------------- 1 | # 876. 链表的中间结点 2 | 3 | https://leetcode-cn.com/problems/middle-of-the-linked-list/ 4 | 5 | - [876. 链表的中间结点](#876-链表的中间结点) 6 | - [题目描述](#题目描述) 7 | - [方法 1: 双指针](#方法-1-双指针) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 给定一个头结点为 head 的非空单链表,返回链表的中间结点。 16 | 17 | 如果有两个中间结点,则返回第二个中间结点。 18 | 19 |   20 | 21 | 示例 1: 22 | 23 | 输入:[1,2,3,4,5] 24 | 输出:此列表中的结点 3 (序列化形式:[3,4,5]) 25 | 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。 26 | 注意,我们返回了一个 ListNode 类型的对象 ans,这样: 27 | ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. 28 | 示例 2: 29 | 30 | 输入:[1,2,3,4,5,6] 31 | 输出:此列表中的结点 4 (序列化形式:[4,5,6]) 32 | 由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 33 |   34 | 35 | 提示: 36 | 37 | 给定链表的结点数介于 1 和 100 之间。 38 | 39 | 来源:力扣(LeetCode) 40 | 链接:https://leetcode-cn.com/problems/middle-of-the-linked-list 41 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 42 | ``` 43 | 44 | ## 方法 1: 双指针 45 | 46 | ### 思路 47 | 48 | 用快慢指针遍历链表,快指针每次走两步,慢指针每次走一步,相同时间内快指针走的距离就是慢指针的两倍,所以,当快指针走到链表尾部的时候,慢指针刚好在链表中间。 49 | 50 | 剩下的问题就是奇数和偶数节点数链表分别怎么处理。 51 | 52 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/876_0.png) 53 | 54 | 看图,结论就是,快指针能继续走的时候继续走就行,不用分情况考虑 (●ˇ∀ˇ●) 55 | 56 | ### 复杂度分析 57 | 58 | - 时间复杂度:$O(N)$, N 为链表长度。 59 | - 空间复杂度:$O(1)$。 60 | 61 | ### 代码 62 | 63 | JavaScript Code 64 | 65 | ```js 66 | /** 67 | * Definition for singly-linked list. 68 | * function ListNode(val) { 69 | * this.val = val; 70 | * this.next = null; 71 | * } 72 | */ 73 | /** 74 | * @param {ListNode} head 75 | * @return {ListNode} 76 | */ 77 | var middleNode = function (head) { 78 | let slow = head, 79 | fast = head; 80 | 81 | while (fast && fast.next) { 82 | slow = slow.next; 83 | fast = fast.next.next; 84 | } 85 | 86 | return slow; 87 | }; 88 | ``` 89 | -------------------------------------------------------------------------------- /basic/two-pointers/29.grumpy-bookstore-owner.md: -------------------------------------------------------------------------------- 1 | # 1052. 爱生气的书店老板 2 | 3 | https://leetcode-cn.com/problems/grumpy-bookstore-owner/ 4 | 5 | - [1052. 爱生气的书店老板](#1052-爱生气的书店老板) 6 | - [题目描述](#题目描述) 7 | - [方法 1:滑动窗口](#方法-1滑动窗口) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码(啰嗦版本)](#代码啰嗦版本) 11 | - [代码(简洁版本)](#代码简洁版本) 12 | - [代码(一次遍历的版本)](#代码一次遍历的版本) 13 | 14 | ## 题目描述 15 | 16 | ``` 17 | 今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。 18 | 19 | 在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。 20 | 21 | 书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。 22 | 23 | 请你返回这一天营业下来,最多有多少客户能够感到满意的数量。 24 |   25 | 26 | 示例: 27 | 28 | 输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], X = 3 29 | 输出:16 30 | 解释: 31 | 书店老板在最后 3 分钟保持冷静。 32 | 感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16. 33 |   34 | 35 | 提示: 36 | 37 | 1 <= X <= customers.length == grumpy.length <= 20000 38 | 0 <= customers[i] <= 1000 39 | 0 <= grumpy[i] <= 1 40 | 41 | 来源:力扣(LeetCode) 42 | 链接:https://leetcode-cn.com/problems/grumpy-bookstore-owner 43 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 44 | ``` 45 | 46 | ## 方法 1:滑动窗口 47 | 48 | ### 思路 49 | 50 | 根据题目信息,书店老板可以 `让自己连续 X 分钟不生气`,那我们可以: 51 | 52 | - 先让他在 `[0, X]` 这段时间不生气,计算此时满意的顾客数。 53 | - 然后让他在 `[1, X+1]` 这段时间不生气,计算满意的顾客。 54 | - ...... 55 | - 让他在 `[N-X, N]` 这段时间不生气,计算满意的顾客。 56 | 57 | 其实就是用一个长度为 X 的滑动窗口来遍历数组,并在这个过程中找出满意顾客数的最大值。 58 | 59 | 关键问题是,如何计算满意顾客的数量?naive 的想法当然是滑动窗口每次滑动时,都遍历一次数组进行计算,但这种做法时间复杂度是 $O(N*(N-X))$,非良解。 60 | 61 | 关于长度固定的滑动窗口,有一点我们要熟悉的是,窗口每次滑动时,变化的只有头尾两个元素,所以我们只针对这两个元素进行处理就行。 62 | 63 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/1052_0.png) 64 | 65 | 具体怎么写代码注释已经很详细啦,下方有三个版本的代码 ╮(╯_╰)╭ 66 | 67 | - 啰嗦版本 68 | - 简洁版本 69 | - 一次遍历版本 70 | 71 | 思路本质上是一样的,复杂度也是一样的,只是写法不同。 72 | 73 | ### 复杂度分析 74 | 75 | - 时间复杂度:$O(N)$,N 为数组长度。 76 | - 空间复杂度:$O(1)$。 77 | 78 | ### 代码(啰嗦版本) 79 | 80 | JavaScript Code 81 | 82 | ```js 83 | /** 84 | * @param {number[]} customers 85 | * @param {number[]} grumpy 86 | * @param {number} X 87 | * @return {number} 88 | */ 89 | var maxSatisfied = function (customers, grumpy, X) { 90 | let total = 0; 91 | 92 | // 首先计算老板不生气时的所有满意顾客 93 | // 这些顾客无论在不在情绪控制区都没有影响的 94 | for (let i = 0; i < customers.length; i++) { 95 | if (!grumpy[i]) total += customers[i]; 96 | } 97 | 98 | // 然后滑出一个长度为 X 的窗口 99 | // 这段是情绪控制区,所以原来不满意的顾客也变成满意的了 100 | let l = 0, 101 | r = 0; 102 | while (r < X) { 103 | if (grumpy[r]) total += customers[r]; 104 | r++; 105 | } 106 | 107 | let max = total; 108 | 109 | // 窗口右移 110 | // r 指针回到滑动窗口的右边界 111 | r--; 112 | while (r < customers.length - 1) { 113 | // l 即将离开情绪控制区,如果原本是不满意的顾客,就恢复不满意啦 114 | if (grumpy[l]) total -= customers[l]; 115 | l++; 116 | 117 | r++; 118 | // r 进入了情绪控制区,不满意顾客变成满意的了 119 | if (grumpy[r]) total += customers[r]; 120 | 121 | if (total > max) max = total; 122 | } 123 | 124 | return max; 125 | }; 126 | ``` 127 | 128 | ### 代码(简洁版本) 129 | 130 | JavaScript Code 131 | 132 | ```js 133 | /** 134 | * @param {number[]} customers 135 | * @param {number[]} grumpy 136 | * @param {number} X 137 | * @return {number} 138 | */ 139 | var maxSatisfied = function (customers, grumpy, X) { 140 | let total = 0; 141 | 142 | // 先计算老板不生气时的所有满意顾客 143 | for (let i = 0; i < customers.length; i++) { 144 | if (!grumpy[i]) total += customers[i]; 145 | } 146 | 147 | let max = total; 148 | 149 | for (let i = 0; i < customers.length; i++) { 150 | // 第一个滑动窗口,如果其中有不满意的顾客,将他们改成满意的 151 | if (i < X) total += grumpy[i] ? customers[i] : 0; 152 | // 滑动窗口右移中,(i - X, i] 这段就是滑动窗口 153 | else { 154 | // 左边界离开情绪控制区,如果原本是不满意的顾客,就恢复不满意啦 155 | if (grumpy[i - X]) total -= customers[i - X]; 156 | // 右边界进入情绪控制区,不满意顾客变成满意的了 157 | if (grumpy[i]) total += customers[i]; 158 | } 159 | if (total > max) max = total; 160 | } 161 | 162 | return max; 163 | }; 164 | ``` 165 | 166 | ### 代码(一次遍历的版本) 167 | 168 | JavaScript Code 169 | 170 | ```js 171 | /** 172 | * @param {number[]} customers 173 | * @param {number[]} grumpy 174 | * @param {number} X 175 | * @return {number} 176 | */ 177 | var maxSatisfied = function (customers, grumpy, X) { 178 | // 老板不生气时的所有满意顾客 179 | let got = 0, 180 | // 老板控制情绪时滑动窗口中增加的满意顾客,也就是滑动窗口中原本不满意的顾客 181 | total = 0, 182 | // 在滑动窗口移动过程中 total 的最大值 183 | max = 0; 184 | 185 | for (let i = 0; i < customers.length; i++) { 186 | if (!grumpy[i]) got += customers[i]; 187 | 188 | // 第一个滑动窗口 189 | if (i < X) total += grumpy[i] ? customers[i] : 0; 190 | // 滑动窗口右移中,(i - X, i] 这段就是滑动窗口 191 | else { 192 | if (grumpy[i - X]) total -= customers[i - X]; 193 | if (grumpy[i]) total += customers[i]; 194 | } 195 | if (total > max) max = total; 196 | } 197 | 198 | // 原本的满意顾客 + 情绪控制区最多能收揽的不满意顾客 199 | return got + max; 200 | }; 201 | ``` 202 | -------------------------------------------------------------------------------- /basic/two-pointers/ext-find-closest-lcci.md: -------------------------------------------------------------------------------- 1 | # 面试题 17.11.单词距离 2 | 3 | https://leetcode-cn.com/problems/find-closest-lcci 4 | 5 | - [面试题 17.11.单词距离](#面试题-1711单词距离) 6 | - [题目描述](#题目描述) 7 | - [方法 1:双指针](#方法-1双指针) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | - [方法 2:哈希表](#方法-2哈希表) 12 | - [思路](#思路-1) 13 | - [复杂度分析](#复杂度分析-1) 14 | - [代码](#代码-1) 15 | 16 | ## 题目描述 17 | 18 | ``` 19 | 有个内含单词的超大文本文件,给定任意两个单词,找出在这个文件中这两个单词的最短距离(相隔单词数)。如果寻找过程在这个文件中会重复多次,而每次寻找的单词不同,你能对此优化吗? 20 | 21 | 示例: 22 | 23 | 输入:words = ["I","am","a","student","from","a","university","in","a","city"], word1 = "a", word2 = "student" 24 | 输出:1 25 | 提示: 26 | 27 | words.length <= 100000 28 | 29 | 来源:力扣(LeetCode) 30 | 链接:https://leetcode-cn.com/problems/find-closest-lcci 31 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 32 | ``` 33 | 34 | ## 方法 1:双指针 35 | 36 | ### 思路 37 | 38 | 使用双指针去找目标词: 39 | 40 | - 当 `指针l` 找到 `word1` 时,`指针r` 从 `指针l` 的右边出发去找 `word1` 或者 `word2`; 41 | - 如果 `指针r` 找到了 `word2`,计算距离 `r - l`,同时记录一个最小的距离; 42 | - 如果 `指针r` 找到的还是 `word1`,更新 `指针l` 到 `指针r` 的位置,`指针r` 继续右移寻找; 43 | 44 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/17.11_0.png) 45 | 46 | ### 复杂度分析 47 | 48 | - 时间复杂度:$O(N)$,N 为数组长度。 49 | - 空间复杂度:$O(1)$。 50 | 51 | ### 代码 52 | 53 | JavaScript Code 54 | 55 | ```js 56 | /** 57 | * @param {string[]} words 58 | * @param {string} word1 59 | * @param {string} word2 60 | * @return {number} 61 | */ 62 | var findClosest = function (words, word1, word2) { 63 | const len = words.length; 64 | 65 | // 找到 word1 或者 word2 66 | const foundTarget = word => [word1, word2].includes(word); 67 | 68 | // e.g. 如果当前是 word1,那下一个要找的是 word2 69 | const getNext = cur => (cur === word1 ? word2 : word1); 70 | 71 | let res = len; 72 | let l = -1, 73 | r = -1, 74 | next = ''; // 下一个目标词 75 | 76 | while (r < len) { 77 | // 找到 word1 或者 word2 78 | if (foundTarget(words[r])) { 79 | // 如果找到的是目标词就更新 res 80 | if (words[r] === next && r - l < res) res = r - l; 81 | 82 | // 获取下一个目标词 83 | next = getNext(words[r]); 84 | 85 | l = r; 86 | r = r + 1; 87 | } else { 88 | r++; 89 | } 90 | } 91 | return res; 92 | }; 93 | ``` 94 | 95 | ## 方法 2:哈希表 96 | 97 | ### 思路 98 | 99 | 先用一个哈希表把每个词出现的位置坐标收集起来,再用两个指针分别遍历两个目标词的坐标数组,计算最短距离。 100 | 101 | 如果寻找过程在这个文件中会重复多次,而每次寻找的单词不同,哈希表的解法更适合。 102 | 103 | > ps. 下图中 a 和 student 的坐标数组不是题目中的真实结果。 104 | 105 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/17.11_1.png) 106 | 107 | ### 复杂度分析 108 | 109 | - 时间复杂度:$O(N)$,N 为数组长度,遍历一次数组记录单词出现位置的时间复杂度 $O(N)$,遍历两个目标单词的位置数组时间复杂度为 $O(N)$。 110 | - 空间复杂度:$O(N)$,N 为数组长度,用了一个哈希表来记录每个单词出现的所有位置。 111 | 112 | ### 代码 113 | 114 | JavaScript Code 115 | 116 | ```js 117 | /** 118 | * @param {string[]} words 119 | * @param {string} word1 120 | * @param {string} word2 121 | * @return {number} 122 | */ 123 | var findClosest = function (words, word1, word2) { 124 | // 记录所有单词出现的位置 125 | const dict = {}; 126 | words.forEach((w, i) => { 127 | dict[w] || (dict[w] = []); 128 | dict[w].push(i); 129 | }); 130 | 131 | const indices1 = dict[word1], 132 | indices2 = dict[word2]; 133 | let p1 = 0, 134 | p2 = 0, 135 | res = words.length; 136 | 137 | while (p1 < indices1.length && p2 < indices2.length) { 138 | res = Math.min(Math.abs(indices2[p2] - indices1[p1]), res); 139 | indices2[p2] > indices1[p1] ? p1++ : p2++; 140 | } 141 | return res; 142 | }; 143 | ``` 144 | -------------------------------------------------------------------------------- /basic/two-pointers/ext-koko-eating-bananas.md: -------------------------------------------------------------------------------- 1 | # 875.爱吃香蕉的珂珂 2 | 3 | https://leetcode-cn.com/problems/koko-eating-bananas 4 | 5 | - [875.爱吃香蕉的珂珂](#875爱吃香蕉的珂珂) 6 | - [题目描述](#题目描述) 7 | - [方法 1:二分法](#方法-1二分法) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。 16 | 17 | 珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。 18 | 19 | 珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。 20 | 21 | 返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。 22 | 23 | 24 | 25 | 示例 1: 26 | 27 | 输入: piles = [3,6,7,11], H = 8 28 | 输出: 4 29 | 示例 2: 30 | 31 | 输入: piles = [30,11,23,4,20], H = 5 32 | 输出: 30 33 | 示例 3: 34 | 35 | 输入: piles = [30,11,23,4,20], H = 6 36 | 输出: 23 37 | 38 | 39 | 提示: 40 | 41 | 1 <= piles.length <= 10^4 42 | piles.length <= H <= 10^9 43 | 1 <= piles[i] <= 10^9 44 | 45 | 来源:力扣(LeetCode) 46 | 链接:https://leetcode-cn.com/problems/koko-eating-bananas 47 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 48 | ``` 49 | 50 | ## 方法 1:二分法 51 | 52 | ### 思路 53 | 54 | 题目要求我们找到珂珂在规定时间内吃完香蕉的最小速度 K。那我们要如何确定 K 的范围呢? 55 | 56 | 显然,珂珂吃香蕉的速度最小应该是 1,而最快则是最大堆香蕉的数量,再快也没有意义了,即 K 的取值范围是 [1, maxPile]。 57 | 58 | 那接下来,符合直觉的做法是,在这个范围内,从 1 开始逐一尝试,看 K 取哪个值的时候珂珂正好能在规定时间内吃完香蕉。这样线性查找的时间复杂度是 $O(N)$,N 等于 maxPile。 59 | 60 | 不过,因为 1 ~ maxPile 是连续递增的,要在一个有序的范围内查找一个值的话,很容易就想到了二分查找。 61 | 62 | - 在范围 [1, maxPile] 中使用二分查找寻找最小速度 K; 63 | - 如果当前速度不够珂珂吃完香蕉,左指针右移,继续寻找; 64 | - 如果当前速度足够珂珂吃完香蕉,记录当前速度,然后右指针左移,继续寻找是否存在满足条件的更小速度; 65 | 66 | ### 复杂度分析 67 | 68 | - 时间复杂度:$O(mlogN)$,N 是最大堆香蕉的数量,m 是香蕉的堆数。二分查找的时间复杂度是 $O(logN)$,检查当前 K 值是否符合要求时遍历 piles 数组的时间复杂度是 $O(m)$。 69 | - 空间复杂度:$O(1)$。 70 | 71 | ### 代码 72 | 73 | JavaScript Code 74 | 75 | ```js 76 | /** 77 | * @param {number[]} piles 78 | * @param {number} H 79 | * @return {number} 80 | */ 81 | var minEatingSpeed = function (piles, H) { 82 | let l = 0, 83 | r = Math.max(...piles), 84 | mid = 0, 85 | res = 0; 86 | 87 | while (l <= r) { 88 | mid = ((l + r) / 2) << 0; 89 | if (isPossible(piles, H, mid)) { 90 | res = mid; 91 | r = mid - 1; 92 | } else { 93 | l = mid + 1; 94 | } 95 | } 96 | 97 | return res; 98 | 99 | // ******************************** 100 | function isPossible(piles, H, K) { 101 | let time = 0; 102 | piles.forEach(p => { 103 | time += Math.ceil(p / K); 104 | }); 105 | return time <= H; 106 | } 107 | }; 108 | ``` 109 | -------------------------------------------------------------------------------- /basic/two-pointers/ext-two-sum-ii-input-array-is-sorted.md: -------------------------------------------------------------------------------- 1 | # 167.两数之和 II - 输入有序数组 2 | 3 | https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted 4 | 5 | - [167.两数之和 II - 输入有序数组](#167两数之和-ii---输入有序数组) 6 | - [题目描述](#题目描述) 7 | - [方法 1:双指针](#方法-1双指针) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 16 | 17 | 函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 18 | 19 | 说明: 20 | 21 | 返回的下标值(index1 和 index2)不是从零开始的。 22 | 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 23 | 示例: 24 | 25 | 输入: numbers = [2, 7, 11, 15], target = 9 26 | 输出: [1,2] 27 | 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 28 | 29 | 来源:力扣(LeetCode) 30 | 链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted 31 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 32 | ``` 33 | 34 | ## 方法 1:双指针 35 | 36 | ### 思路 37 | 38 | 使用 `left` 和 `right` 两个指针分别从数组的两端开始遍历数组;因为数组是升序排序的,所以: 39 | 40 | - 当两指针的数字相加小于 `target` 时,我们要把两数中较小的数字换成一个更大的数字,`left` 指针右移一步; 41 | - 当两指针的数字相加大于 `target` 时,我们要把两数中较大的数字换成一个更小的数字,`right` 指针需要左移一步; 42 | - 当两指针的数字相加等于 `target` 时,返回两指针下标加一。 43 | 44 | ### 复杂度分析 45 | 46 | - 时间复杂度:$O(N)$,N 为数组长度。 47 | - 空间复杂度:$O(1)$。 48 | 49 | ### 代码 50 | 51 | JavaScript Code 52 | 53 | ```js 54 | /** 55 | * @param {number[]} numbers 56 | * @param {number} target 57 | * @return {number[]} 58 | */ 59 | var twoSum = function (numbers, target) { 60 | let l = 0, 61 | r = numbers.length - 1, 62 | sum = 0; 63 | 64 | while (l < r) { 65 | sum = numbers[l] + numbers[r]; 66 | 67 | if (sum === target) { 68 | return [l + 1, r + 1]; 69 | } 70 | 71 | sum < target ? l++ : r--; 72 | } 73 | }; 74 | ``` 75 | 76 | Python Code 77 | 78 | ```py 79 | class Solution(object): 80 | def twoSum(self, numbers, target): 81 | """ 82 | :type numbers: List[int] 83 | :type target: int 84 | :rtype: List[int] 85 | """ 86 | l, r = 0, len(numbers) - 1 87 | while l < r: 88 | s = numbers[l] + numbers[r] 89 | if s < target: l += 1 90 | elif s > target: r -= 1 91 | else: return [l + 1, r + 1] 92 | ``` 93 | -------------------------------------------------------------------------------- /extensions/04.09.bst-sequences-lcci.md: -------------------------------------------------------------------------------- 1 | # 面试题 04.09. 二叉搜索树序列 2 | 3 | https://leetcode-cn.com/problems/bst-sequences-lcci/ 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 从左向右遍历一个数组,通过不断将其中的元素插入树中可以逐步地生成一棵二叉搜索树。给定一个由不同节点组成的二叉搜索树,输出所有可能生成此树的数组。 9 | 10 |   11 | 12 | 示例: 13 | 给定如下二叉树 14 | 15 | 2 16 | / \ 17 | 1 3 18 | 返回: 19 | 20 | [ 21 | [2,1,3], 22 | [2,3,1] 23 | ] 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode-cn.com/problems/bst-sequences-lcci 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | ``` 29 | 30 | ## 思路 31 | 32 | TODO 33 | 34 | ## 复杂度分析 35 | 36 | - 时间复杂度:$O()$。 37 | - 空间复杂度:$O()$。 38 | 39 | ## 代码 40 | 41 | JavaScript Code 42 | 43 | ```js 44 | /** 45 | * Definition for a binary tree node. 46 | * function TreeNode(val) { 47 | * this.val = val; 48 | * this.left = this.right = null; 49 | * } 50 | */ 51 | /** 52 | * @param {TreeNode} root 53 | * @return {number[][]} 54 | */ 55 | var BSTSequences = function (root) { 56 | var res = dfs(root); 57 | return res; 58 | 59 | // **************************** 60 | 61 | function dfs(node) { 62 | if (!node) return [[]]; 63 | if (!node.left && !node.right) return [[node.val]]; 64 | 65 | // 左子树所有可能的序列 66 | var left = dfs(node.left); 67 | // 右子树所有可能的序列 68 | var right = dfs(node.right); 69 | // 组合 70 | var res = []; 71 | 72 | for (let l of left) { 73 | for (let r of right) { 74 | // 左右子树的组合不止两种,具体看 merge 函数的注释 75 | var merged = merge(l, r); 76 | // 父节点总是在开头 77 | res.push(...merged.map(el => [node.val, ...el])); 78 | } 79 | } 80 | return res; 81 | } 82 | 83 | /** 84 | * 返回 arr1 和 arr2 的所有排列组合 85 | * @param arr1 86 | * @param arr2 87 | */ 88 | function merge(arr1, arr2) { 89 | if (arr1.length == 0 && arr2.length == 0) { 90 | return []; 91 | } 92 | if (arr1.length == 0) return [arr2]; 93 | if (arr2.length == 0) return [arr1]; 94 | 95 | var res1 = []; 96 | if (arr1.length > 0) { 97 | // 设左子树的根节点为 p,右子树的根节点为 q 98 | // 插入 p 后,接下来可以插入 p 的子节点或者 q 99 | var merged = merge(arr1.slice(1), arr2); 100 | res1 = merged.map(sub => [arr1[0], ...sub]); 101 | } 102 | var res2 = []; 103 | if (arr2.length > 0) { 104 | // 设左子树的根节点为 p,右子树的根节点为 q 105 | // 插入 q 后,接下来可以插入 q 的子节点或者 p 106 | var merged = merge(arr1, arr2.slice(1)); 107 | res2 = merged.map(sub => [arr2[0], ...sub]); 108 | } 109 | return [...res1, ...res2]; 110 | } 111 | }; 112 | ``` 113 | -------------------------------------------------------------------------------- /extensions/77.combination.md: -------------------------------------------------------------------------------- 1 | # 77. 组合 2 | 3 | https://leetcode-cn.com/problems/combinations/ 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 9 | 10 | 示例: 11 | 12 | 输入: n = 4, k = 2 13 | 输出: 14 | [ 15 | [2,4], 16 | [3,4], 17 | [2,3], 18 | [1,2], 19 | [1,3], 20 | [1,4], 21 | ] 22 | 23 | 来源:力扣(LeetCode) 24 | 链接:https://leetcode-cn.com/problems/combinations 25 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 26 | ``` 27 | 28 | ## 思路 29 | 30 | **DFS** 31 | 32 | - 递归枚举所有组合。 33 | - 对一个数字,有选和不选两种可能,做出选择后,再递归地对后面的数字进行选择。 34 | 35 | **剪枝** 36 | 37 | 在递归解法的基础上还可以做一些剪枝的优化。 38 | 39 | 比如如果剩下的数字加上临时数组里面的数字个数小于 `k`,那这个解就可以提前放弃了。 40 | 41 | ```js 42 | // prune 43 | var leftNums = n - cur + 1; 44 | if (leftNums < k - arr.length) return; 45 | ``` 46 | 47 | ## 复杂度分析 48 | 49 | - 时间复杂度:$O()$。TODO 50 | - 空间复杂度:$O(n)$,调用栈的高度是 $O(n)$,临时数组的长度是 $O(k)$。 51 | 52 | ## 代码 53 | 54 | JavaScript Code 55 | 56 | ```js 57 | /** 58 | * @param {number} n 59 | * @param {number} k 60 | * @return {number[][]} 61 | */ 62 | var combine = function (n, k) { 63 | var res = []; 64 | dfs(1, []); 65 | return res; 66 | 67 | // ********************** 68 | 69 | function dfs(cur, arr) { 70 | if (arr.length === k) { 71 | res.push([...arr]); 72 | return; 73 | } 74 | if (cur > n) return; 75 | 76 | // prune 77 | var leftNums = n - cur + 1; 78 | if (leftNums < k - arr.length) return; 79 | 80 | // pick 81 | arr.push(cur); 82 | dfs(cur + 1, arr); 83 | 84 | // not pick 85 | arr.pop(); 86 | dfs(cur + 1, arr); 87 | } 88 | }; 89 | ``` 90 | -------------------------------------------------------------------------------- /extensions/925.long-pressed-name.md: -------------------------------------------------------------------------------- 1 | # 925. 长按键入 2 | 3 | https://leetcode-cn.com/problems/long-pressed-name/ 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 你的朋友正在使用键盘输入他的名字 name。偶尔,在键入字符 c 时,按键可能会被长按,而字符可能被输入 1 次或多次。 9 | 10 | 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),那么就返回 True。 11 | 12 |   13 | 14 | 示例 1: 15 | 16 | 输入:name = "alex", typed = "aaleex" 17 | 输出:true 18 | 解释:'alex' 中的 'a' 和 'e' 被长按。 19 | 示例 2: 20 | 21 | 输入:name = "saeed", typed = "ssaaedd" 22 | 输出:false 23 | 解释:'e' 一定需要被键入两次,但在 typed 的输出中不是这样。 24 | 示例 3: 25 | 26 | 输入:name = "leelee", typed = "lleeelee" 27 | 输出:true 28 | 示例 4: 29 | 30 | 输入:name = "laiden", typed = "laiden" 31 | 输出:true 32 | 解释:长按名字中的字符并不是必要的。 33 |   34 | 35 | 提示: 36 | 37 | name.length <= 1000 38 | typed.length <= 1000 39 | name 和 typed 的字符都是小写字母。 40 | 41 | 来源:力扣(LeetCode) 42 | 链接:https://leetcode-cn.com/problems/long-pressed-name 43 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 44 | ``` 45 | 46 | ## 思路 47 | 48 | ## 复杂度分析 49 | 50 | - 时间复杂度:$O(M+N)$,M 和 N 分别是两个字符串的长度。 51 | - 空间复杂度:$O(1)$。 52 | 53 | ## 代码 54 | 55 | JavaScript Code 56 | 57 | ```js 58 | /** 59 | * @param {string} name 60 | * @param {string} typed 61 | * @return {boolean} 62 | */ 63 | var isLongPressedName = function (name, typed) { 64 | var p1 = 0, 65 | p2 = 0; 66 | 67 | while (p2 < typed.length) { 68 | if (p1 < name.length && name[p1] === typed[p2]) { 69 | p1++; 70 | p2++; 71 | } else { 72 | // 长按字符去重 73 | while (typed[p2] === typed[p2 - 1]) { 74 | p2++; 75 | } 76 | // 及时比较,避免陷入死循环 77 | if (name[p1] !== typed[p2]) return false; 78 | } 79 | } 80 | return p1 === name.length && p2 === typed.length; 81 | }; 82 | ``` 83 | -------------------------------------------------------------------------------- /extensions/lcof_51-II.和为s的连续正数序列.md: -------------------------------------------------------------------------------- 1 | # 剑指 Offer 57 - II. 和为 s 的连续正数序列 2 | 3 | https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/ 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。 9 | 10 | 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。 11 | 12 |   13 | 14 | 示例 1: 15 | 16 | 输入:target = 9 17 | 输出:[[2,3,4],[4,5]] 18 | 示例 2: 19 | 20 | 输入:target = 15 21 | 输出:[[1,2,3,4,5],[4,5,6],[7,8]] 22 |   23 | 24 | 限制: 25 | 26 | 1 <= target <= 10^5 27 | 28 | 来源:力扣(LeetCode) 29 | 链接:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof 30 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 31 | ``` 32 | 33 | ## 代码 34 | 35 | JavaScript Code 36 | 37 | ```js 38 | /** 39 | * @param {number} target 40 | * @return {number[][]} 41 | */ 42 | var findContinuousSequence = function (target) { 43 | const ans = []; 44 | 45 | const dfs = (n, sum, path) => { 46 | if (sum === target) path.length > 1 && ans.push([...path]); 47 | if (sum >= target) return; 48 | n++; 49 | dfs(n, sum + n, [...path, n]); 50 | }; 51 | 52 | for (let i = 1; i < target; i++) { 53 | dfs(i, i, [i]); 54 | } 55 | return ans; 56 | }; 57 | ``` 58 | -------------------------------------------------------------------------------- /medium/day-43.md: -------------------------------------------------------------------------------- 1 | # 面试题 17.17.多次搜索 2 | 3 | https://leetcode-cn.com/problems/multi-search-lcci 4 | 5 | - Trie 6 | - 暴力 7 | 8 | ## 题目描述 9 | 10 | ``` 11 | 给定一个较长字符串big和一个包含较短字符串的数组smalls,设计一个方法,根据smalls中的每一个较短字符串,对big进行搜索。输出smalls中的字符串在big里出现的所有位置positions,其中positions[i]为smalls[i]出现的所有位置。 12 | 13 | 示例: 14 | 15 | 输入: 16 | big = "mississippi" 17 | smalls = ["is","ppi","hi","sis","i","ssippi"] 18 | 输出: [[1,4],[8],[],[3],[1,4,7,10],[5]] 19 | 提示: 20 | 21 | 0 <= len(big) <= 1000 22 | 0 <= len(smalls[i]) <= 1000 23 | smalls的总字符数不会超过 100000。 24 | 你可以认为smalls中没有重复字符串。 25 | 所有出现的字符均为英文小写字母。 26 | 27 | 来源:力扣(LeetCode) 28 | 链接:https://leetcode-cn.com/problems/multi-search-lcci 29 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 30 | ``` 31 | 32 | ## Trie 33 | 34 | ### 思路 35 | 36 | 用 Trie 有两个思路方向,一个是把 `big` 存进 Trie 中,另一个是把 `smalls` 存进 Trie 中。 37 | 38 | 但由于 Trie 这种数据结构非常消耗空间,所以,当我们要在一个长字符串中查找短字符串时,正确的直觉应该是把短字符串存到 Trie 中,保证 Trie 的高度要尽量的小。 39 | 40 | - 以 `smalls` 数组构建 Trie,并在结束节点记录每个短串在 `smalls` 数组中的下标; 41 | - 遍历 `big`,截取所有以 `longest` 为长度的子串(`longest` 是 `smalls` 中最长的单词长度),拿到 Trie 中去寻找所有匹配的短串,返回所有匹配到的下标,根据下标把当前子串的位置更新到对应的结果数组中。 42 | 43 | > 截取 longest 长度的字符子串这步并不是必须,但 JS 中函数参数是按值传递的,所以如果每次都传整个 big 字符串,感觉是不是也有点消耗空间? 44 | 45 | ### 复杂度分析 46 | 47 | - 时间复杂度:`insert` 是 $O(n*m)$,n 是 `smalls` 的长度,m 是短串的平均长度,`search` 48 | - 空间复杂度: 49 | 50 | ### 代码 51 | 52 | Trie 的修改: 53 | 54 | - `insert`: 把单词的下标存在最后的节点中 55 | - `search`: 需要返回寻找路径中匹配到的所有单词的下标 56 | 57 | TypeScript Code 58 | 59 | ```ts 60 | search(word: string): Array { 61 | let crawl: TrieNode = this.root 62 | 63 | const res: Array = [] 64 | for (let char of word) { 65 | const index: number = this._char2Index(char) 66 | if (!crawl.children[index]) return res 67 | crawl = crawl.children[index] 68 | if (crawl.pos > -1) res.push(crawl.pos) 69 | } 70 | return res 71 | } 72 | ``` 73 | 74 | TypeScript Code 75 | 76 | ```ts 77 | function multiSearch(big: string, smalls: string[]): number[][] { 78 | // 把短字符串存进 Trie 79 | const trie: Trie = new Trie() 80 | smalls.forEach((word: string, index: number): void => { 81 | trie.insert(word, index) 82 | }) 83 | 84 | const res: number[][] = Array(smalls.length) 85 | .fill(0) 86 | .map(() => []) 87 | 88 | // 找到 smalls 中最长字符串长度 89 | const longest: number = smalls.reduce( 90 | (res: number, word: string): number => Math.max(res, word.length), 91 | 0, 92 | ) 93 | 94 | // 遍历 big,将以 longest 为长度的子串拿到 Trie 中去找有没有匹配的短串 95 | // 有的话,会返回那个短串在 smalls 中对应的下标,那就把子串对应的开始下标 i 存在对应的结果数组里好了 96 | for (let i = 0; i < big.length; i++) { 97 | const indices = trie.search(big.slice(i, i + longest)) 98 | indices.forEach(index => res[index].push(i)) 99 | } 100 | 101 | return res 102 | } 103 | ``` 104 | 105 | ## 暴力 106 | 107 | ### 思路 108 | 109 | 又写了下暴力法,先找出 `smalls` 中最长的单词长度 `longest`,遍历 `big`,然后在第二层循环中,枚举所有长度小于 `longest` 的子串,跟 `smalls` 中的词一一对比。 110 | 111 | ### 代码 112 | 113 | JavaScript 114 | 115 | ```js 116 | /** 117 | * @param {string} big 118 | * @param {string[]} smalls 119 | * @return {number[][]} 120 | */ 121 | function multiSearch(big, smalls) { 122 | const longest = smalls.reduce((res, w) => Math.max(res, w.length), 0) 123 | 124 | const res = Array(smalls.length) 125 | .fill(0) 126 | .map(() => []) 127 | 128 | for (let i = 0; i < big.length; i++) { 129 | for (let j = i + 1; j <= i + longest && j <= big.length; j++) { 130 | const subStr = big.slice(i, j) 131 | for (let k = 0; k < smalls.length; k++) { 132 | if (smalls[k] === subStr) { 133 | res[k].push(i) 134 | } 135 | } 136 | } 137 | } 138 | 139 | return res 140 | } 141 | ``` 142 | 143 | **官方题解** 144 | 145 | https://github.com/leetcode-pp/91alg-1/issues/70#issuecomment-657434460 146 | -------------------------------------------------------------------------------- /medium/day-46.md: -------------------------------------------------------------------------------- 1 | # 1319.连通网络的操作次数 2 | 3 | https://github.com/leetcode-pp/91alg-1/issues/73 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 a 和 b。 9 | 10 | 网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。 11 | 12 | 给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1 。 13 | 14 | 示例 1: 15 | 16 | 17 | 18 | 输入:n = 4, connections = [[0,1],[0,2],[1,2]] 19 | 输出:1 20 | 解释:拔下计算机 1 和 2 之间的线缆,并将它插到计算机 1 和 3 上。 21 | 示例 2: 22 | 23 | 24 | 25 | 输入:n = 6, connections = [[0,1],[0,2],[0,3],[1,2],[1,3]] 26 | 输出:2 27 | 示例 3: 28 | 29 | 输入:n = 6, connections = [[0,1],[0,2],[0,3],[1,2]] 30 | 输出:-1 31 | 解释:线缆数量不足。 32 | 示例 4: 33 | 34 | 输入:n = 5, connections = [[0,1],[0,2],[3,4],[2,3]] 35 | 输出:0 36 | 37 | 38 | 提示: 39 | 40 | 1 <= n <= 10^5 41 | 1 <= connections.length <= min(n*(n-1)/2, 10^5) 42 | connections[i].length == 2 43 | 0 <= connections[i][0], connections[i][1] < n 44 | connections[i][0] != connections[i][1] 45 | 没有重复的连接。 46 | 两台计算机不会通过多条线缆连接。 47 | ``` 48 | 49 | ## 思路 50 | 51 | 一开始的思路: 52 | 53 | - 先构建并查集,这样我们就能知道一共有多少个网络了,接着我们需要把网络连接起来。 54 | - 要连接 n 个网络,需要 n - 1 根网线,那怎么知道我们有多少条多余的网线? 55 | - 很简单,在构建网络时,也就是合并不交集的时候,如果两个节点原本就在同一集合中,那我们就多出来一根网线了。 56 | - 最后判断一下多余网线是否不小于 n - 1 即可。 57 | 58 | 但其实没有必要记录多余的网线,只要算一下数学就好了: 59 | 60 | - 如果连接数小于 `n - 1`,那就直接返回 -1; 61 | - 否则,说明网线是足够连接 n 个节点的,构建完并查集之后,返回集合数量减一即可。 62 | 63 | ## 代码 64 | 65 | TypeScript Code 66 | 67 | ```ts 68 | function makeConnected(n: number, connections: number[][]): number { 69 | const cables: number = connections.length 70 | if (cables < n - 1) return -1 71 | 72 | const uf: UnionFind = new UnionFind(n) 73 | for (let c of connections) { 74 | uf.unionSet(c[0], c[1]) 75 | } 76 | return uf.size() - 1 77 | } 78 | ``` 79 | 80 | ```ts 81 | class UnionFind { 82 | private parents: Array 83 | private rank: Array 84 | private numOfSets: number 85 | 86 | constructor(size: number) { 87 | this.parents = Array(size) 88 | .fill(0) 89 | .map((_, i) => i) 90 | this.rank = Array(size).fill(0) 91 | this.numOfSets = size 92 | } 93 | 94 | size(): number { 95 | return this.numOfSets 96 | } 97 | 98 | findSet(x: number): number { 99 | if (x !== this.parents[x]) { 100 | this.parents[x] = this.findSet(this.parents[x]) 101 | } 102 | return this.parents[x] 103 | } 104 | 105 | unionSet(x: number, y: number): void { 106 | const px: number = this.findSet(x) 107 | const py: number = this.findSet(y) 108 | if (px === py) return 109 | if (this.rank[px] > this.rank[py]) { 110 | this.parents[py] = px 111 | } else { 112 | this.parents[px] = py 113 | this.rank[px] === this.rank[py] && ++this.rank[py] 114 | } 115 | this.numOfSets-- 116 | } 117 | } 118 | ``` 119 | -------------------------------------------------------------------------------- /medium/day-47.md: -------------------------------------------------------------------------------- 1 | # 1206.设计跳表 2 | 3 | https://leetcode-cn.com/problems/design-skiplist 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 不使用任何库函数,设计一个跳表。 9 | 10 | 跳表是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。 11 | 12 | 例如,一个跳表包含 [30, 40, 50, 60, 70, 90],然后增加 80、45 到跳表中,以下图的方式操作: 13 | 14 | 15 | 16 | Artyom Kalinin [CC BY-SA 3.0], via Wikimedia Commons 17 | 18 | 跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(n)),空间复杂度是 O(n)。 19 | 20 | 在本题中,你的设计应该要包含这些函数: 21 | 22 | bool search(int target) : 返回target是否存在于跳表中。 23 | void add(int num): 插入一个元素到跳表。 24 | bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回false. 如果存在多个 num ,删除其中任意一个即可。 25 | 了解更多 : https://en.wikipedia.org/wiki/Skip_list 26 | 27 | 注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。 28 | 29 | 样例: 30 | 31 | Skiplist skiplist = new Skiplist(); 32 | 33 | skiplist.add(1); 34 | skiplist.add(2); 35 | skiplist.add(3); 36 | skiplist.search(0); // 返回 false 37 | skiplist.add(4); 38 | skiplist.search(1); // 返回 true 39 | skiplist.erase(0); // 返回 false,0 不在跳表中 40 | skiplist.erase(1); // 返回 true 41 | skiplist.search(1); // 返回 false,1 已被擦除 42 | 约束条件: 43 | 44 | 0 <= num, target <= 20000 45 | 最多调用 50000 次 search, add, 以及 erase操作。 46 | 47 | 来源:力扣(LeetCode) 48 | 链接:https://leetcode-cn.com/problems/design-skiplist 49 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 50 | ``` 51 | 52 | **参考题解** 53 | 54 | https://github.com/leetcode-pp/91alg-1/issues/74#issuecomment-660018285 55 | -------------------------------------------------------------------------------- /medium/day-48.md: -------------------------------------------------------------------------------- 1 | # 814.二叉树剪枝 2 | 3 | https://leetcode-cn.com/problems/binary-tree-pruning 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定二叉树根结点 root ,此外树的每个结点的值要么是 0,要么是 1。 9 | 10 | 返回移除了所有不包含 1 的子树的原二叉树。 11 | 12 | ( 节点 X 的子树为 X 本身,以及所有 X 的后代。) 13 | 14 | 示例1: 15 | 输入: [1,null,0,0,1] 16 | 输出: [1,null,0,null,1] 17 | 18 | 示例2: 19 | 输入: [1,0,1,0,0,0,1] 20 | 输出: [1,null,1,null,1] 21 | 22 | 示例3: 23 | 输入: [1,1,0,1,1,0,1,0] 24 | 输出: [1,1,0,1,1,null,1] 25 | 26 | 说明: 27 | 28 | 给定的二叉树最多有 100 个节点。 29 | 每个节点的值只会为 0 或 1 。 30 | 31 | 来源:力扣(LeetCode) 32 | 链接:https://leetcode-cn.com/problems/binary-tree-pruning 33 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 34 | ``` 35 | 36 | ## 思路 37 | 38 | 本题解由 lucifer 著名的[产品经理法](https://github.com/leetcode-pp/91alg-1/issues/32#issuecomment-643620727)倾情支持。 39 | 40 | **产品** 41 | 42 | 假设我们已经有了一个 `pruneTree` 方法,可以把一棵树中不包含 `1` 的枝节删掉。 43 | 44 | **子问题** 45 | 46 | 明显就是 `pruneTree(root.left)` 和 `pruneTree(root.right)` 啦。 47 | 48 | **大小问题的关系** 49 | 50 | 首先,我们用 `pruneTree(root.left)` 和 `pruneTree(root.right)` 的结果分别替换掉原本的左子树和右子树。接着,再决定这棵树要不要保留。 51 | 52 | - 如果此时左右子树有一个不为空的话,那说明这棵树是要保留的,直接返回 `root` 就行。 53 | - 如果左右子树都为空,那我们就判断 `root.val` 的值,等于 1 就返回 `root`,等于 0 就返回 `null` 把这棵树移除。 54 | 55 | **递归出口** 56 | 57 | 空节点直接返回 `null` 就行。 58 | 59 | ## 代码 60 | 61 | TypeScript Code 62 | 63 | ```ts 64 | /** 65 | * Definition for a binary tree node. 66 | * class TreeNode { 67 | * val: number 68 | * left: TreeNode | null 69 | * right: TreeNode | null 70 | * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { 71 | * this.val = (val===undefined ? 0 : val) 72 | * this.left = (left===undefined ? null : left) 73 | * this.right = (right===undefined ? null : right) 74 | * } 75 | * } 76 | */ 77 | 78 | function pruneTree(root: TreeNode | null): TreeNode | null { 79 | if (!root) return null 80 | 81 | root.left = pruneTree(root.left) 82 | root.right = pruneTree(root.right) 83 | 84 | return root.left || root.right || root.val === 1 ? root : null 85 | } 86 | ``` 87 | 88 | ## 复杂度分析 89 | 90 | - 时间复杂度:$O(N)$,N 为二叉树节点数。 91 | - 空间复杂度:$O(H)$,H 为二叉树的高度,递归时调用栈占用的最大空间就是二叉树的最大深度。 92 | 93 | **参考题解** 94 | 95 | https://github.com/leetcode-pp/91alg-1/issues/75#issuecomment-660483924 96 | -------------------------------------------------------------------------------- /medium/day-49.md: -------------------------------------------------------------------------------- 1 | # 39.组合总和 2 | 3 | https://leetcode-cn.com/problems/combination-sum 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 9 | 10 | candidates 中的数字可以无限制重复被选取。 11 | 12 | 说明: 13 | 14 | 所有数字(包括 target)都是正整数。 15 | 解集不能包含重复的组合。 16 | 示例 1: 17 | 18 | 输入:candidates = [2,3,6,7], target = 7, 19 | 所求解集为: 20 | [ 21 | [7], 22 | [2,2,3] 23 | ] 24 | 示例 2: 25 | 26 | 输入:candidates = [2,3,5], target = 8, 27 | 所求解集为: 28 | [ 29 | [2,2,2,2], 30 | [2,3,3], 31 | [3,5] 32 | ] 33 | 34 | 35 | 提示: 36 | 37 | 1 <= candidates.length <= 30 38 | 1 <= candidates[i] <= 200 39 | candidate 中的每个元素都是独一无二的。 40 | 1 <= target <= 500 41 | 42 | 来源:力扣(LeetCode) 43 | 链接:https://leetcode-cn.com/problems/combination-sum 44 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 45 | ``` 46 | 47 | ## 思路 48 | 49 | 因为题目是求“所有可能的组合”,所以很自然想到了递归。感觉一提到组合我的第一反应就是,“对于每个元素,都有选与不选两种选择”,对每个元素分别作出选择后我们就得到了其中一个组合。 50 | 51 | - 首先我们需要一个篮子来存放当前组合的元素, 52 | - 然后,如果当前数字大于 `target`,那就直接跳过不考虑。 53 | - 如果当前数字不大于 `target`: 54 | - 选:我们就把当前数字放篮子里,然后对后面的数字进行递归(注意:因为允许重复选择,所以“后面的数字”也包括当前数字) 55 | - 不选:把数字从篮子里拿出来(回溯),然后对后面的数字进行递归(不包括当前数字) 56 | - 递归出口:如果篮子里的数字和大于 `target` 了,那我们就把这个篮子扔掉不管(剪枝);如果刚好等于 `target`,那就把篮子加到要返回的结果中,然后结束递归。 57 | 58 | ## 复杂度分析 59 | 60 | - 时间复杂度:时间复杂度我就从 n 叉树的角度来想吧,也不知道对不对,$O(len(candidates)^{target/min(candidates)})$。 61 | - 空间复杂度:调用栈的空间我感觉是 $O(target/min(candidates))$,一个组合最长的情况就是所有元素都是 min(candidate),所以返回结果的空间应该是......不会算了... 62 | 63 | ## 代码 64 | 65 | JavaScript Code 66 | 67 | ```js 68 | /** 69 | * @param {number[]} candidates 70 | * @param {number} target 71 | * @return {number[][]} 72 | */ 73 | var combinationSum = function (candidates, target) { 74 | const dfs = (candidates, ans, remain, cur) => { 75 | if (remain < 0) return 76 | 77 | if (remain === 0) { 78 | res.push(ans) 79 | return 80 | } 81 | 82 | while (cur < candidates.length) { 83 | if (candidates[cur] <= remain) { 84 | ans.push(candidates[cur]) 85 | dfs(candidates, [...ans], remain - candidates[cur], cur) 86 | ans.pop() 87 | } 88 | cur++ 89 | } 90 | } 91 | 92 | const res = [] 93 | dfs(candidates, [], target, 0) 94 | return res 95 | } 96 | ``` 97 | -------------------------------------------------------------------------------- /medium/day-50.md: -------------------------------------------------------------------------------- 1 | # 40. 组合总和 II 2 | 3 | https://leetcode-cn.com/problems/combination-sum-ii 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 9 | 10 | candidates 中的每个数字在每个组合中只能使用一次。 11 | 12 | 说明: 13 | 14 | 所有数字(包括目标数)都是正整数。 15 | 解集不能包含重复的组合。 16 | 示例 1: 17 | 18 | 输入: candidates = [10,1,2,7,6,1,5], target = 8, 19 | 所求解集为: 20 | [ 21 | [1, 7], 22 | [1, 2, 5], 23 | [2, 6], 24 | [1, 1, 6] 25 | ] 26 | 示例 2: 27 | 28 | 输入: candidates = [2,5,2,1,2], target = 5, 29 | 所求解集为: 30 | [ 31 | [1,2,2], 32 | [5] 33 | ] 34 | 35 | 来源:力扣(LeetCode) 36 | 链接:https://leetcode-cn.com/problems/combination-sum-ii 37 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 38 | ``` 39 | 40 | ## 思路 41 | 42 | ## 复杂度分析 43 | 44 | - 时间复杂度: 45 | - 空间复杂度: 46 | 47 | ## 代码 48 | 49 | JavaScript Code 50 | 51 | ```js 52 | /** 53 | * @param {number[]} candidates 54 | * @param {number} target 55 | * @return {number[][]} 56 | */ 57 | var combinationSum2 = function (candidates, target) { 58 | const helper = (candidates, arr, remain, cur, res) => { 59 | for (let i = cur; i < candidates.length; i++) { 60 | if (candidates[i] > remain) continue 61 | if (candidates[i] === candidates[i - 1] && i > cur) continue 62 | 63 | if (candidates[i] === remain) { 64 | res.push([...arr, candidates[i]]) 65 | return 66 | } 67 | 68 | arr.push(candidates[i]) 69 | helper(candidates, [...arr], remain - candidates[i], i + 1, res) 70 | arr.pop() 71 | } 72 | } 73 | 74 | const res = [] 75 | candidates.sort((a, b) => a - b) 76 | helper(candidates, [], target, 0, res) 77 | return res 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /medium/day-51.md: -------------------------------------------------------------------------------- 1 | # 47. 全排列 II 2 | 3 | https://leetcode-cn.com/problems/permutations-ii 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定一个可包含重复数字的序列,返回所有不重复的全排列。 9 | 10 | 示例: 11 | 12 | 输入: [1,1,2] 13 | 输出: 14 | [ 15 | [1,1,2], 16 | [1,2,1], 17 | [2,1,1] 18 | ] 19 | 20 | 来源:力扣(LeetCode) 21 | 链接:https://leetcode-cn.com/problems/permutations-ii 22 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 23 | 24 | 25 | ``` 26 | 27 | ## 思路 28 | 29 | ## 复杂度分析 30 | 31 | - 时间复杂度: 32 | - 空间复杂度: 33 | 34 | ## 代码 35 | 36 | JavaScript Code 37 | 38 | ```js 39 | /** 40 | * @param {number[]} nums 41 | * @return {number[][]} 42 | */ 43 | var permuteUnique = function (nums) { 44 | const backtrack = (nums, path, res, visited) => { 45 | if (path.length === nums.length) { 46 | res.push(path); 47 | return; 48 | } 49 | 50 | for (let i = 0; i < nums.length; i++) { 51 | if ( 52 | visited[i] || 53 | (i > 0 && nums[i] === nums[i - 1] && visited[i - 1]) 54 | ) 55 | continue; 56 | visited[i] = true; 57 | path.push(nums[i]); 58 | backtrack(nums, [...path], res, visited); 59 | path.pop(); 60 | visited[i] = false; 61 | } 62 | }; 63 | 64 | const res = []; 65 | nums.sort((a, b) => a - b); 66 | backtrack(nums, [], res, Array(nums.length)); 67 | return res; 68 | }; 69 | ``` 70 | -------------------------------------------------------------------------------- /medium/day-52.md: -------------------------------------------------------------------------------- 1 | # 1371. 每个元音包含偶数次的最长子字符串 2 | 3 | https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。 9 | 10 | 11 | 12 | 示例 1: 13 | 14 | 输入:s = "eleetminicoworoep" 15 | 输出:13 16 | 解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。 17 | 示例 2: 18 | 19 | 输入:s = "leetcodeisgreat" 20 | 输出:5 21 | 解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。 22 | 示例 3: 23 | 24 | 输入:s = "bcbcbc" 25 | 输出:6 26 | 解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。 27 | 28 | 29 | 提示: 30 | 31 | 1 <= s.length <= 5 x 10^5 32 | s 只包含小写英文字母。 33 | 34 | 来源:力扣(LeetCode) 35 | 链接:https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts 36 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 37 | ``` 38 | 39 | ## 思路 40 | 41 | ## 复杂度分析 42 | 43 | - 时间复杂度: 44 | - 空间复杂度: 45 | 46 | ## 代码 47 | 48 | JavaScript Code 49 | 50 | ```js 51 | /** 52 | * @param {string} s 53 | * @return {number} 54 | */ 55 | var findTheLongestSubstring = function (s) { 56 | const map = { 57 | '00000': -1, 58 | }; 59 | const cnt = { 60 | a: 0, 61 | e: 0, 62 | i: 0, 63 | o: 0, 64 | u: 0, 65 | }; 66 | const vowels = Object.keys(cnt); 67 | let res = 0; 68 | 69 | for (let i = 0; i < s.length; i++) { 70 | s[i] in cnt && cnt[s[i]]++; 71 | 72 | let temp = ''; 73 | for (let v of vowels) { 74 | temp += (cnt[v] % 2) + ''; 75 | } 76 | 77 | if (temp in map) { 78 | res = Math.max(res, i - map[temp]); 79 | } else { 80 | map[temp] = i; 81 | } 82 | } 83 | return res; 84 | }; 85 | ``` 86 | -------------------------------------------------------------------------------- /medium/day-53.md: -------------------------------------------------------------------------------- 1 | # 面试题 17.13 恢复空格 2 | 3 | https://leetcode-cn.com/problems/re-space-lcci 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!"已经变成了"iresetthecomputeritstilldidntboot"。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。 9 | 10 | 注意:本题相对原题稍作改动,只需返回未识别的字符数 11 | 12 | 13 | 14 | 示例: 15 | 16 | 输入: 17 | dictionary = ["looked","just","like","her","brother"] 18 | sentence = "jesslookedjustliketimherbrother" 19 | 输出: 7 20 | 解释: 断句后为"jess looked just like tim her brother",共7个未识别字符。 21 | 提示: 22 | 23 | 0 <= len(sentence) <= 1000 24 | dictionary中总字符数不超过 150000。 25 | 你可以认为dictionary和sentence中只包含小写字母。 26 | 27 | 来源:力扣(LeetCode) 28 | 链接:https://leetcode-cn.com/problems/re-space-lcci 29 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 30 | ``` 31 | 32 | ## 思路 33 | 34 | ## 复杂度分析 35 | 36 | - 时间复杂度: 37 | - 空间复杂度: 38 | 39 | ## 代码 40 | 41 | JavaScript Code 42 | 43 | ```js 44 | ``` 45 | -------------------------------------------------------------------------------- /medium/hot/35.reverse-linked-list-ii.md: -------------------------------------------------------------------------------- 1 | # 92.反转链表 II 2 | 3 | https://leetcode-cn.com/problems/reverse-linked-list-ii/ 4 | 5 | - [92.反转链表 II](#92反转链表-ii) 6 | - [题目描述](#题目描述) 7 | - [方法 1: 两趟扫描](#方法-1-两趟扫描) 8 | - [思路](#思路) 9 | - [代码](#代码) 10 | - [方法 2: 一趟扫描](#方法-2-一趟扫描) 11 | - [思路](#思路-1) 12 | - [代码](#代码-1) 13 | 14 | ## 题目描述 15 | 16 | ``` 17 | 反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。 18 | 19 | 说明: 20 | 1 ≤ m ≤ n ≤ 链表长度。 21 | 22 | 示例: 23 | 24 | 输入: 1->2->3->4->5->NULL, m = 2, n = 4 25 | 输出: 1->4->3->2->5->NULL 26 | 27 | 来源:力扣(LeetCode) 28 | 链接:https://leetcode-cn.com/problems/reverse-linked-list-ii 29 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 30 | ``` 31 | 32 | ## 方法 1: 两趟扫描 33 | 34 | ### 思路 35 | 36 | 比较直白的思路: 37 | 38 | - 第一趟扫描先找到需要反转的链表片段的头尾, 39 | - 然后再扫描一遍将这个片段进行反转, 40 | - 最后将反转后的片段和原链表的头尾两段拼接起来就好。 41 | 42 | 思路很简单,就是各种指针有点绕,具体看代码吧。 43 | 44 | ### 代码 45 | 46 | JavaScript Code 47 | 48 | ```js 49 | /** 50 | * Definition for singly-linked list. 51 | * function ListNode(val) { 52 | * this.val = val; 53 | * this.next = null; 54 | * } 55 | */ 56 | /** 57 | * @param {ListNode} head 58 | * @param {number} m 59 | * @param {number} n 60 | * @return {ListNode} 61 | */ 62 | var reverseBetween = function (head, m, n) { 63 | if (!head || !head.next) return head; 64 | 65 | let startPrev = null, // 第 m-1 个节点 66 | start = null, // 第 m 个节点 67 | end = null, // 第 n 个节点 68 | endNext = null, // 第 n+1 个节点 69 | cur = head; 70 | 71 | // 第一趟扫描 72 | // 分别找到上述节点 73 | let i = 1; 74 | while (cur && i <= n) { 75 | if (i < m) { 76 | startPrev = cur; 77 | } 78 | if (i === m) { 79 | start = cur; 80 | } 81 | if (i === n) { 82 | end = cur; 83 | endNext = cur.next; 84 | } 85 | cur = cur.next; 86 | i++; 87 | } 88 | 89 | // 将 m -> n 这段链表进行反转 90 | // reverseList 可以用循环实现,具体看 206 题 91 | end.next = null; 92 | end = reverseList(start); 93 | 94 | // 如果反转不是从第一个节点开始的话 95 | // 将反转后的片段和原链表左边的那段拼接起来 96 | if (startPrev) { 97 | startPrev.next = end; 98 | } 99 | // 如果反转是从第一个节点开始的话 100 | // 把 head 指针指向反转后片段的第一个节点 101 | else { 102 | head = end; 103 | } 104 | // 将反转后的片段和原链表右边的那段拼接起来 105 | start.next = endNext; 106 | return head; 107 | }; 108 | 109 | /** 110 | * @param {ListNode} head 111 | * @return {ListNode} 112 | */ 113 | var reverseList = function (head) { 114 | if (!head || !head.next) return head; 115 | const lastNode = head.next; 116 | const newHead = reverseList(head.next); 117 | lastNode.next = head; 118 | head.next = null; 119 | return newHead; 120 | }; 121 | ``` 122 | 123 | ## 方法 2: 一趟扫描 124 | 125 | ### 思路 126 | 127 | 从第 `m` 个节点开始,不断地把这个节点的 `next` 拿出来,插到第 `m-1` 个节点后面,直到第 `n-1` 个节点,结束操作。 128 | 129 | 思路很简单,就是链表的删除和插入操作,画个图就比较好理解了。 130 | 131 | > tips: 用一个 dummy 节点来简化操作,这样就不用分开考虑 m 等于 1 和不等于 1 的情况了。 132 | 133 | ### 代码 134 | 135 | JavaScript Code 136 | 137 | ```js 138 | /** 139 | * Definition for singly-linked list. 140 | * function ListNode(val) { 141 | * this.val = val; 142 | * this.next = null; 143 | * } 144 | */ 145 | /** 146 | * @param {ListNode} head 147 | * @param {number} m 148 | * @param {number} n 149 | * @return {ListNode} 150 | */ 151 | var reverseBetween = function (head, m, n) { 152 | if (!head || !head.next) return head; 153 | 154 | const dummy = new ListNode(null); 155 | dummy.next = head; 156 | 157 | let prev = dummy, 158 | cur = prev.next; 159 | 160 | let i = 0; 161 | while (i < n - 1) { 162 | i++; 163 | 164 | // prev 是反转片段前的一个节点 165 | if (i === m - 1) { 166 | prev = cur; 167 | } 168 | if (i < m) { 169 | cur = cur.next; 170 | } 171 | 172 | // 在 [m, n) 这段区间里 173 | // 把 cur 的下一个节点从链表中删掉 174 | // 再插入到 prev 后面去 175 | if (i >= m) { 176 | temp = cur.next; 177 | cur.next = temp.next; 178 | temp.next = prev.next; 179 | prev.next = temp; 180 | } 181 | } 182 | return dummy.next; 183 | }; 184 | ``` 185 | -------------------------------------------------------------------------------- /medium/hot/35.reverse-linked-list.md: -------------------------------------------------------------------------------- 1 | # 206.反转链表 2 | 3 | https://leetcode-cn.com/problems/reverse-linked-list/ 4 | 5 | - [206.反转链表](#206反转链表) 6 | - [题目描述](#题目描述) 7 | - [方法 1: 递归](#方法-1-递归) 8 | - [思路](#思路) 9 | - [代码](#代码) 10 | - [方法 2: 循环](#方法-2-循环) 11 | - [思路](#思路-1) 12 | - [图解](#图解) 13 | - [代码](#代码-1) 14 | 15 | ## 题目描述 16 | 17 | ``` 18 | 反转一个单链表。 19 | 20 | 示例: 21 | 22 | 输入: 1->2->3->4->5->NULL 23 | 输出: 5->4->3->2->1->NULL 24 | 进阶: 25 | 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 26 | 27 | 来源:力扣(LeetCode) 28 | 链接:https://leetcode-cn.com/problems/reverse-linked-list 29 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 30 | ``` 31 | 32 | ## 方法 1: 递归 33 | 34 | ### 思路 35 | 36 | 假设我们已经有了一个 `F()` 函数,它的功能就是反转链表,然后返回反转后链表的头部。 37 | 38 | **发现子问题** 39 | 40 | 假设我们有一个链表,这个链表是不是可以分成两个部分:`head` 和 `head.next 到链表尾部`。 41 | 42 | 那要反转这个链表,是不是可以先把 `head.next 到链表尾部` 这段进行反转,也就是说,要解决 `F(head)`,我们可以先解决 `F(head.next)`。 43 | 44 | **发现大问题和小问题的关系** 45 | 46 | 很简单,只要把反转后的 `head.next 到链表尾部` 这段的最后一个节点指向 `head` 就好了。 47 | 48 | 但是 `F()` 函数只会返回反转后链表的头部啊,我们怎么知道链表的最后一个节点啊? 49 | 50 | 在调用 `F(head.next)` 之前先记录一下 `head.next` 就好啦,具体看代码注释吧。 51 | 52 | **递归出口** 53 | 54 | - 链表最后一个节点:只剩一个节点,直接返回它就好了。 55 | - 空节点。 56 | 57 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/reverse-a-linked-list-recursive.png) 58 | 59 | ### 代码 60 | 61 | JavaScript Code 62 | 63 | ```js 64 | /** 65 | * Definition for singly-linked list. 66 | * function ListNode(val) { 67 | * this.val = val; 68 | * this.next = null; 69 | * } 70 | */ 71 | /** 72 | * @param {ListNode} head 73 | * @return {ListNode} 74 | */ 75 | var reverseList = function (head) { 76 | // 递归出口 77 | if (!head || !head.next) return head; 78 | 79 | // 进行反转下一段链表之前, 80 | // 先把这段链表的第一个节点记录一下 81 | // 链表反转结束后,这就变成最后一个节点了 82 | const lastNode = head.next; 83 | 84 | // 反转余下的链表 85 | // reverseList 函数会返回反转后链表的头部 86 | const newHead = reverseList(head.next); 87 | 88 | // 将反转后链表的尾部指向当前节点 89 | lastNode.next = head; 90 | head.next = null; 91 | // 返回新头部 92 | return newHead; 93 | }; 94 | ``` 95 | 96 | ## 方法 2: 循环 97 | 98 | ### 思路 99 | 100 | 1. 初始化一个 `prev` 指针为 null,一个 `cur` 指针为 head; 101 | 2. 开始遍历链表,在每一次循环中: 102 | 103 | - 先保存 `cur.next`; 104 | - 把 `cur.next` 倒转方向指向 `prev`; 105 | - `prev` 和 `cur` 都分别往前一步; 106 | 107 | ### 图解 108 | 109 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/reverse-a-linked-list-loop.png) 110 | 111 | ### 代码 112 | 113 | ```py 114 | # Definition for singly-linked list. 115 | # class ListNode(object): 116 | # def __init__(self, x): 117 | # self.val = x 118 | # self.next = None 119 | 120 | class Solution(object): 121 | def reverseList(self, head): 122 | """ 123 | :type head: ListNode 124 | :rtype: ListNode 125 | """ 126 | prev, cur = None, head 127 | while cur != None: 128 | nextNode = cur.next 129 | cur.next = prev 130 | prev = cur 131 | cur = nextNode 132 | return prev 133 | ``` 134 | -------------------------------------------------------------------------------- /medium/hot/35.reverse-nodes-in-k-group.md: -------------------------------------------------------------------------------- 1 | # 25.K 个一组翻转链表 2 | 3 | https://leetcode-cn.com/problems/reverse-nodes-in-k-group/ 4 | 5 | - [25.K 个一组翻转链表](#25k-个一组翻转链表) 6 | - [题目描述](#题目描述) 7 | - [原题](#原题) 8 | - [思路](#思路) 9 | - [代码](#代码) 10 | - [变形 1](#变形-1) 11 | - [题目修改](#题目修改) 12 | - [思路](#思路-1) 13 | - [代码](#代码-1) 14 | - [变形 2](#变形-2) 15 | - [题目修改](#题目修改-1) 16 | - [思路](#思路-2) 17 | - [代码](#代码-2) 18 | 19 | ## 题目描述 20 | 21 | ``` 22 | 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。 23 | 24 | k 是一个正整数,它的值小于或等于链表的长度。 25 | 26 | 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。 27 | 28 |   29 | 30 | 示例: 31 | 32 | 给你这个链表:1->2->3->4->5 33 | 34 | 当 k = 2 时,应当返回: 2->1->4->3->5 35 | 36 | 当 k = 3 时,应当返回: 3->2->1->4->5 37 | 38 |   39 | 40 | 说明: 41 | 42 | 你的算法只能使用常数的额外空间。 43 | 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。 44 | 45 | 来源:力扣(LeetCode) 46 | 链接:https://leetcode-cn.com/problems/reverse-nodes-in-k-group 47 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 48 | ``` 49 | 50 | ## 原题 51 | 52 | ### 思路 53 | 54 | 遍历链表,用一个计数器 `i` 来记录当前是第几个节点,当 `i` 等于 `k` 的倍数时,将 `i` 之前的这段链表进行反转,也就是调用一下 `reverseBetween(ithNode, i - k + 1, i)` 就行,把反转后的链表插回原来的位置,然后继续遍历。 55 | 56 | ### 代码 57 | 58 | JavaScript Code 59 | 60 | ```js 61 | // 不想写 62 | ``` 63 | 64 | ## 变形 1 65 | 66 | ### 题目修改 67 | 68 | ``` 69 | 将原题中 70 | 71 | “如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序” 72 | 73 | 改为: 74 | 75 | “如果节点总数不是 k 的整数倍,那么请将最前面剩余的节点保持原有顺序” 76 | ``` 77 | 78 | ### 思路 79 | 80 | 先遍历一次链表找到剩余节点的个数(用一个 counter 记录,数到 k 就归零),也就是 `len % k`,然后从第 `len % k + 1` 个节点进行 k 个一组的反转。 81 | 82 | ### 代码 83 | 84 | JavaScript Code 85 | 86 | ```js 87 | // 不想写 88 | ``` 89 | 90 | ## 变形 2 91 | 92 | ### 题目修改 93 | 94 | ``` 95 | 将原题中 96 | 97 | “如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序” 98 | 99 | 改为: 100 | 101 | “如果节点总数不是 k 的整数倍,那么请将最后剩余的节点作为一组进行反转” 102 | ``` 103 | 104 | ### 思路 105 | 106 | 跟[92.反转链表 II](./35.reverse-linked-list-ii.md)的思路有点像,都是反转链表中的一段,不过这题是以 `K` 个节点为一组地反转。 107 | 108 | 大概思路是,用一个计数器 `i` 记录当前是第几个节点,如果 `i` 是 0 或者 `K` 的倍数的话,说明接下来的 `K` 个节点是一组,对它们进行反转操作就行。 109 | 110 | ### 代码 111 | 112 | JavaScript Code 113 | 114 | ```js 115 | /** 116 | * Definition for singly-linked list. 117 | * function ListNode(val) { 118 | * this.val = val; 119 | * this.next = null; 120 | * } 121 | */ 122 | /** 123 | * @param {ListNode} head 124 | * @param {number} k 125 | * @return {ListNode} 126 | */ 127 | var reverseKGroup = function (head, k) { 128 | if (!head || !head.next || k <= 1) return head; 129 | 130 | const dummy = new ListNode(null); 131 | dummy.next = head; 132 | 133 | let prev = dummy, 134 | cur = prev.next; 135 | 136 | let i = 0; 137 | while (cur) { 138 | i++; 139 | if (i % k === 0) { 140 | prev = cur; 141 | cur = prev.next; 142 | } 143 | if (!cur || !cur.next) break; 144 | 145 | const temp = cur.next; 146 | cur.next = temp.next; 147 | temp.next = prev.next; 148 | prev.next = temp; 149 | } 150 | return dummy.next; 151 | }; 152 | ``` 153 | -------------------------------------------------------------------------------- /medium/hot/36.subsets.md: -------------------------------------------------------------------------------- 1 | # 78.子集 2 | 3 | https://leetcode-cn.com/problems/subsets 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 9 | 10 | 说明:解集不能包含重复的子集。 11 | 12 | 示例: 13 | 14 | 输入: nums = [1,2,3] 15 | 输出: 16 | [ 17 | [3], 18 | [1], 19 | [2], 20 | [1,2,3], 21 | [1,3], 22 | [2,3], 23 | [1,2], 24 | [] 25 | ] 26 | 27 | 来源:力扣(LeetCode) 28 | 链接:https://leetcode-cn.com/problems/subsets 29 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 30 | ``` 31 | 32 | ## 方法 1:回溯 33 | 34 | ### 思路 35 | 36 | 如果我们要生成**一个**子集,那步骤大概是: 37 | 38 | - 初始化一个数组用来表示这个子集; 39 | - 遍历数组,对每个元素做出选择: 40 | - 把它放进子集中; 41 | - 不把它放进子集中; 42 | - 遍历结束后就得到了一个子集; 43 | 44 | 那如果要生成所有的子集的话: 45 | 46 | - 当我们对当前元素做出选择后,可以递归地去对数组剩余的其他元素做选择; 47 | - 等递归到数组的最后一个元素,也就是说,我们已经遍历完一轮数组,对每个元素都做出了选择,那我们就得到了一个子集,把这个子集存到一个全局数组变量中; 48 | - 等到 DFS 结束后返回这个全局变量就行; 49 | 50 | ### 复杂度分析 51 | 52 | - 时间复杂度:由排列组合原理可知,一共有 $2^N$ 种组合,因此时间复杂度为 $O(2^N)$,其中 $N$ 为数字的个数。 53 | - 空间复杂度:由于调用栈深度最多为 $N$,且临时数组长度不会超过 $N$,因此空间复杂度为 $O(N)$,其中 $N$ 为数字的个数。 54 | 55 | ### 代码 56 | 57 | TypeScript Code 58 | 59 | ```ts 60 | function subsets(nums: number[]): number[][] { 61 | const res: number[][] = []; 62 | 63 | const dfs = (nums: number[], cur: number, sub: number[]): void => { 64 | if (cur === nums.length) { 65 | res.push(sub); 66 | return; 67 | } 68 | // exclude the current element 69 | dfs(nums, cur + 1, sub); 70 | // include the current element 71 | dfs(nums, cur + 1, [...sub, nums[cur]]); 72 | }; 73 | 74 | dfs(nums, 0, []); 75 | return res; 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /medium/hot/38.valid-parentheses.md: -------------------------------------------------------------------------------- 1 | # 20.有效括号 2 | 3 | https://leetcode-cn.com/problems/valid-parentheses/ 4 | 5 | - [20.有效括号](#20有效括号) 6 | - [题目描述](#题目描述) 7 | - [方法 1:栈](#方法-1栈) 8 | - [思路](#思路) 9 | - [代码](#代码) 10 | - [复杂度分析](#复杂度分析) 11 | - [方法 2:递归](#方法-2递归) 12 | - [思路](#思路-1) 13 | - [代码](#代码-1) 14 | - [复杂度分析](#复杂度分析-1) 15 | - [方法 3:正则](#方法-3正则) 16 | - [思路](#思路-2) 17 | - [代码](#代码-2) 18 | - [复杂度分析](#复杂度分析-2) 19 | 20 | ## 题目描述 21 | 22 | ``` 23 | 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 24 | 25 | 有效字符串需满足: 26 | 27 | 左括号必须用相同类型的右括号闭合。 28 | 左括号必须以正确的顺序闭合。 29 | 注意空字符串可被认为是有效字符串。 30 | 31 | 示例 1: 32 | 33 | 输入: "()" 34 | 输出: true 35 | 示例 2: 36 | 37 | 输入: "()[]{}" 38 | 输出: true 39 | 示例 3: 40 | 41 | 输入: "(]" 42 | 输出: false 43 | 示例 4: 44 | 45 | 输入: "([)]" 46 | 输出: false 47 | 示例 5: 48 | 49 | 输入: "{[]}" 50 | 输出: true 51 | 52 | 来源:力扣(LeetCode) 53 | 链接:https://leetcode-cn.com/problems/valid-parentheses 54 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 55 | ``` 56 | 57 | ## 方法 1:栈 58 | 59 | ### 思路 60 | 61 | - 遇到左括号就入栈。 62 | - 遇到右括号就从栈中弹出一个元素,判断两者是否是匹配的一对括号。 63 | 64 | ### 代码 65 | 66 | JavaScript Code 67 | 68 | ```js 69 | /** 70 | * @param {string} s 71 | * @return {boolean} 72 | */ 73 | var isValid = function (s) { 74 | const dict = { 75 | '}': '{', 76 | ')': '(', 77 | ']': '[', 78 | }; 79 | const stack = []; 80 | 81 | for (let c of s) { 82 | if (c in dict) { 83 | if (stack.pop() !== dict[c]) { 84 | return false; 85 | } 86 | } else { 87 | stack.push(c); 88 | } 89 | } 90 | return stack.length === 0; 91 | }; 92 | ``` 93 | 94 | ### 复杂度分析 95 | 96 | - 时间复杂度:$O(n)$,n 为字符串的长度。 97 | - 空间复杂度:$O(n)$,n 为字符串的长度。 98 | 99 | ## 方法 2:递归 100 | 101 | ### 思路 102 | 103 | 产品经理法(⓿_⓿) 假设我们现在已经有了一个 `isValid` 方法,可以验证一个字符串 s 是否由有效括号组成。 104 | 105 | **那么子问题是什么?** 106 | 107 | 如果 s 的第一个字符是左括号的话,那我们应该可以找到它对应右括号的下标 index,如果找不到说明就不是有效括号啦。 108 | 109 | 如果找到了,那么现在问题就变成了: 110 | 111 | - (0, index) 中间这段字符串是否包含有效括号? 112 | - (index, s.length) 这段字符串是否包含有效括号? 113 | 114 | > 开括号表示不包括边界 115 | 116 | **大问题和小问题的关系是什么?** 117 | 118 | 显然必须两个小问题的结果都是 true 才行啦,也就是: 119 | 120 | `isValid(s) = isValid(1, index - 1) && isValid(index + 1, s.length)` 121 | 122 | **递归出口在哪里?** 123 | 124 | - 如果字符串第一个字符不是左括号,`return false` 125 | - 如果字符串为空,`return true` 126 | - 还有一个,如果字符串长度是奇数的话,我们也可以提前 `return false`。 127 | 128 | **关于如何找到对应的右括号下标** 129 | 130 | 用一个 `counter` 来记录当前遇到的括号,比如,我们要找的是 '(' 的对应右括号 ')',那么在遍历字符串时: 131 | 132 | - 遇到 '(' 时 `counter++` 133 | - 遇到 ')' 时 `counter--` 134 | - 当 `counter === 0` 时说明我们找到了 135 | - 如果遍历结束后 `counter > 0` 说明找不到匹配的右括号 136 | 137 | ### 代码 138 | 139 | JavaScript Code 140 | 141 | ```js 142 | const dict = { 143 | '(': ')', 144 | '{': '}', 145 | '[': ']', 146 | }; 147 | 148 | const matchClose = (s, start, end, open, close) => { 149 | let count = 0; 150 | for (let i = start; i <= end; i++) { 151 | if (s[i] === open) count++; 152 | if (s[i] === close) count--; 153 | if (count === 0) return i; 154 | } 155 | return -1; 156 | }; 157 | 158 | /** 159 | * @param {string} s 160 | * @return {boolean} 161 | */ 162 | const isValid = function (s) { 163 | if (!s) return true; 164 | if (s.length % 2 === 1) return false; 165 | if (!(s[0] in dict)) return false; 166 | 167 | const closeIndex = matchClose(s, 0, s.length - 1, s[0], dict[s[0]]); 168 | if (closeIndex === -1) return false; 169 | return ( 170 | isValid(s.slice(1, closeIndex)) && 171 | isValid(s.slice(closeIndex + 1, s.length)) 172 | ); 173 | }; 174 | ``` 175 | 176 | ### 复杂度分析 177 | 178 | - 时间复杂度:TODO 179 | - 空间复杂度:$O(n)$,n 为字符串的长度,调用栈的最大深度是 $n/2$。 180 | 181 | ## 方法 3:正则 182 | 183 | ### 思路 184 | 185 | 不断地把 `()` `{}` `[]` 替换成空字符,直到: 186 | 187 | - s 变成了空字符,那么结果就是 `true`,或者, 188 | - s 长度不为空,但是 s 中已经没有 `()`,`{}` 或者 `[]` 括号对了,那么结果就是 `false`。 189 | 190 | ### 代码 191 | 192 | JavaScript Code 193 | 194 | ```js 195 | /** 196 | * @param {string} s 197 | * @return {boolean} 198 | */ 199 | const isValid = function (s) { 200 | const reg = /\[\]|\(\)|\{\}/; 201 | while (reg.test(s)) { 202 | s = s.replace(reg, ''); 203 | } 204 | return s.length === 0; 205 | }; 206 | ``` 207 | 208 | ### 复杂度分析 209 | 210 | - 时间复杂度:$O(n)$,n 为字符串的长度。最坏的情况下,每次循环只能替换一对括号,比如 "(((((())))))",需要循环 n/2 次。 211 | - 空间复杂度:$O(1)$。 212 | -------------------------------------------------------------------------------- /medium/hot/40.netease.md: -------------------------------------------------------------------------------- 1 | # 网易面试题 2 | 3 | - [网易面试题](#网易面试题) 4 | - [题目描述](#题目描述) 5 | - [方法 1:前缀和](#方法-1前缀和) 6 | - [思路](#思路) 7 | - [复杂度分析](#复杂度分析) 8 | - [代码](#代码) 9 | - [输入输出](#输入输出) 10 | 11 | ## 题目描述 12 | 13 | ``` 14 | 有一个班级有 n 个人,给出 n 个元素,第 i 个元素代表 第 i 位同学的考试成绩,接下进行 m 次询问,每次询问给出一个数值 t ,表示第 t 个同学,然后需要我们输出第 t 个同学的成绩超过班级百分之几的人,百分数 p 可以这样算:p = (不超过第 t 个同学分数的人数 ) / n * 100%。输出的时候保留到小数点后 6 位,并且需要四舍五入。 15 | 16 | 输入描述:第一行输入两个数 n 和 m,两个数以空格隔开,表示 n 个同学和 m 次询问。第二行输入 n 个数值 ni,表示每个同学的分数,第三行输入 m 个数值mi,表示每次询问是询问第几个同学。(注意,这里 2<=n,m<=100000,0<=ni<=150,1<=mi<=n) 17 | 18 | 输出描述:输出 m 行,每一行输出一个百分数 p,代表超过班级百分之几的人。 19 | 20 | 示例1: 21 | 22 | 输入 : 23 | 24 | 3 2 25 | 26 | 50 60 70 27 | 28 | 1 2 29 | 30 | 输出 31 | 32 | 33.333333% 33 | 34 | 66.666667% 35 | ``` 36 | 37 | ## 方法 1:前缀和 38 | 39 | ### 思路 40 | 41 | 一开始我的思路是,先将 `scores` 数组排序,这样就能很容易算出 “有多少人不超过当前分数” 这个前缀和。但如果要将 `scores` 排序的话,还得另外记录 “第 mi 位同学” 排序后是第几位,这样就很麻烦,一点都不优雅。 42 | 43 | 今天看到 lucifer 的[公众号推文](https://mp.weixin.qq.com/s?__biz=MzI4MzUxNjI3OA==&mid=2247484133&idx=1&sn=8870dab18460b703b533554348bfbc2d&chksm=eb88cefcdcff47eaadeacd973aeb8f121f38d391836609634938012d4928c3ae05821b7de413&mpshare=1&scene=1&srcid=0709I7wtNzjBsdJCb6Scn047&sharer_sharetime=1594257607942&sharer_shareid=941e688bd70e39dd8c5830d69244a606&key=2cc21493b77c18de0932e4abe179e6b8bf5276701b5903d43b2a4e994c0a9c41c980ea9d342c09ca426a66ab8d432d35ba7ac74719fdf9190813c24066e3e8f0f6d8a6f4d9e2cdb1b6ca7e8164f7abb8&ascene=1&uin=MjY3NjAyNDkwOA%3D%3D&devicetype=Windows+10+x64&version=62090529&lang=zh_CN&exportkey=A5Dnu8F36bmWI9kheu9OEKA%3D&pass_ticket=EMd5fQITaaViEIM88E0TkfQdPbU3%2BdQeQ5UwK3eYZOalQ092cZJXi01KYB7Q9wyk)后,才发现原来可以把“分数”这个值本身当作数组下标啊,所以思路就变成了: 44 | 45 | - 因为`分数`的范围是 `[0, 150]`,所以我们可以用一个长度为 150 的数组 `prefix` 来记录每个分数出现的次数,数组下标是`分数值`,数组的值就是分数出现的 `次数`; 46 | - 遍历 `scores` 数组,对于每个分数 `s`,`prefix[s]++`; 47 | - 遍历 `prefix`,计算前缀和; 48 | - 然后我们就可以实现 $O(1)$ 时间的: 49 | - 询问第 `mi` 位同学的分数能超过班上百分之几的人 50 | - 通过 `scores[mi]` 找到这位同学的分数 `s` 51 | - 通过 `prefix[s]` 找到不超过分数 `s` 的人数 52 | - 算数学吧 53 | 54 | ### 复杂度分析 55 | 56 | - 时间复杂度:$O(N)$,N 是数组长度。 57 | - 空间复杂度:$O(max(scores))$,前缀和数组,取决于分数范围。 58 | 59 | ### 代码 60 | 61 | JavaScript Code 62 | 63 | ```js 64 | /** 65 | * @param {number[]} scores 全班同学的分数 66 | * @param {number} mi 第 mi 个同学 67 | * @return {number} 第 mi 个同学的分数能超过班级百分之几的人 68 | */ 69 | var test = function (scores, mi) { 70 | // 下标是分数,值是“有多少人是这个分数” 71 | const prefix = Array(151).fill(0); 72 | 73 | // 先遍历 scores 数组,统计每个分数的人数 74 | scores.forEach(s => prefix[s]++); 75 | 76 | // 计算前缀和 77 | // 下标是分数,值是“有多少人不超过这个分数 <=” 78 | for (let i = 1; i < prefix.length; i++) { 79 | prefix[i] += prefix[i - 1]; 80 | } 81 | 82 | // 第 mi 个同学的分数 83 | const score = scores[mi - 1]; 84 | 85 | const p = (prefix[score] / scores.length) * 100; 86 | return p.toFixed(6) + '%'; 87 | }; 88 | ``` 89 | 90 | ### 输入输出 91 | 92 | ```js 93 | const __main__ = function () { 94 | const readline = require('readline'); 95 | const rl = readline.createInterface({ 96 | input: process.stdin, 97 | output: process.stdout, 98 | }); 99 | 100 | console.log('******输入******'); 101 | rl.prompt(); 102 | const inputs = []; 103 | rl.on('line', line => inputs.push(line)); 104 | 105 | rl.on('close', () => { 106 | const [n, m] = inputs[0].split(' '); // n 个同学, m 次询问 107 | const scores = inputs[1].split(' ').slice(0, n); // 全班同学的分数 108 | const asks = inputs[2].split(' ').slice(0, m); // 每次询问的是第几位同学 109 | 110 | console.log('\n******输出******'); 111 | asks.forEach(mi => { 112 | const p = test(scores, mi); 113 | console.log(p); 114 | }); 115 | }); 116 | }; 117 | ``` 118 | 119 | 更多题解可以访问:[https://github.com/suukii/91-days-algorithm](https://github.com/suukii/91-days-algorithm) 120 | -------------------------------------------------------------------------------- /medium/hot/40.subarray-sum-equals-k.md: -------------------------------------------------------------------------------- 1 | # 560.和为 K 的子数组 2 | 3 | https://leetcode-cn.com/problems/subarray-sum-equals-k/ 4 | 5 | - [560.和为 K 的子数组](#560和为-k-的子数组) 6 | - [题目描述](#题目描述) 7 | - [方法 1:前缀和](#方法-1前缀和) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ### 题目描述 13 | 14 | ``` 15 | 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。 16 | 17 | 示例 1 : 18 | 19 | 输入:nums = [1,1,1], k = 2 20 | 输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。 21 | 说明 : 22 | 23 | 数组的长度为 [1, 20,000]。 24 | 数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。 25 | 26 | 来源:力扣(LeetCode) 27 | 链接:https://leetcode-cn.com/problems/subarray-sum-equals-k 28 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 29 | ``` 30 | 31 | ## 方法 1:前缀和 32 | 33 | ### 思路 34 | 35 | 比较直白的想法是,先构建前缀和数组 `prefix`,计算以 i 结束的子数组的和,然后在这个数组中找到所有满足条件的 `[i, j]` 区间,也就是 `prefix[j] - prefix[i]` 等于 K。但这样找得两层循环了,时间复杂度比较高。 36 | 37 | 有没有只需要遍历一遍数组的方法呢?其实只要算一点点数学就好了: 38 | 39 | - 我们要找的一段区间 `[i, j]` 需要满足 `prefix[j] - prefix[i] == k` (i < j)。 40 | - 也就是当我们在遍历到 `j` 这个位置的时候,只要往 `j` 的左边去找到有没有 `prefix[i]` 等于 `prefix[j] - k` 就行,满足条件的 `prefix[i]` 可能有一个或多个哦。 41 | - 在遍历到 `j` 之前,我们已经遍历过 `i` 了 (i < j),所以我们只需要在遍历到 `i` 的时候用一个哈希表把 `prefix[i]` 存起来,就能实现 $O(1)$ 时间的查找。 42 | 43 | 其实我们连 `prefix` 数组都不需要,因为我们在算出一个前缀和的时候,就已经把它存到哈希表里面去了。所以可以只用一个变量 `prefix` 来计算前缀和,在遍历 `nums` 数组的过程中不断更新 `prefix`,同时检查 `map[prefix - k]` 是否在之前出现过。 44 | 45 | ### 复杂度分析 46 | 47 | - 时间复杂度:$O(n)$, n 为数组长度,只扫描了一次数组。 48 | - 空间复杂度:$O(n)$, n 为数组长度,使用了一个哈希表来存每个前缀和出现的次数。 49 | 50 | ### 代码 51 | 52 | JavaScript Code 53 | 54 | ```js 55 | /** 56 | * @param {number[]} nums 57 | * @param {number} k 58 | * @return {number} 59 | */ 60 | var subarraySum = function (nums, k) { 61 | const map = {}; 62 | let count = 0; 63 | 64 | let prefix = 0; 65 | map[prefix] = 1; 66 | 67 | for (let i = 0; i < nums.length; i++) { 68 | prefix += nums[i]; 69 | 70 | if (prefix - k in map) { 71 | count += map[prefix - k]; 72 | } 73 | 74 | map[prefix] = (map[prefix] || 0) + 1; 75 | } 76 | 77 | return count; 78 | }; 79 | ``` 80 | 81 | 更多题解可以访问:[https://github.com/suukii/91-days-algorithm](https://github.com/suukii/91-days-algorithm) 82 | -------------------------------------------------------------------------------- /topics/day-62.md: -------------------------------------------------------------------------------- 1 | # 69. x 的平方根 2 | 3 | https://leetcode-cn.com/problems/sqrtx 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 实现 int sqrt(int x) 函数。 9 | 10 | 计算并返回 x 的平方根,其中 x 是非负整数。 11 | 12 | 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 13 | 14 | 示例 1: 15 | 16 | 输入: 4 17 | 输出: 2 18 | 示例 2: 19 | 20 | 输入: 8 21 | 输出: 2 22 | 说明: 8 的平方根是 2.82842..., 23 | 由于返回类型是整数,小数部分将被舍去 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode-cn.com/problems/sqrtx 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | ``` 29 | 30 | ## 思路 31 | 32 | 这个问题分两种情况: 33 | 34 | 1. x 有整数平方根 35 | 2. x 没有整数平方根 36 | 37 | 第 1 种就是最基本的**二分法查找目标值**,而第 2 种可以转化成**寻找最右边的满足条件的值**,在这个问题里,这个条件就是 target 的平方小于 x (因为题目要求结果只保留整数部分)。 38 | 39 | - 首先定义搜索区间为 [l, r],注意左右都是闭区间。 40 | - 在循环过程中,如果碰到 m 平方等于 x 就可以提前返回了。 41 | - 如果 m 平方小于 x,收缩左边界,如果 m 平方大于 x,收缩右边界。 42 | - 最后搜索区间会被缩小到只剩一个数字 n,如果 n 不是 x 的整数平方根,那么还剩两种情况: 43 | 1. 如果 $n^2 > x$,那么 n-1 就是我们想要的结果,而由于 n 平方大于 x 时我们会收缩右边界,此时右指针会左移,刚好指向 n-1,同时结束了循环,最后我们返回右指针 r 即可。 44 | 2. 如果 $n^2 < x$,那么 n 就是我们想要的结果,由于 n 平方小于 x 时左边界会收缩,此时左指针右移,右指针不动,依然指向 n,循环结束,最后我们还是返回右指针 r。 45 | - 所以循环结束后我们直接返回右指针 r 即可。 46 | 47 | 需要特别注意一下的是 0 和 1 这两个数字,不过上面的算法对这两个数字也是有效的。 48 | 49 | ## 复杂度 50 | 51 | - 时间复杂度:$O(logx)$ 52 | - 空间复杂度:$O(1)$ 53 | 54 | ## 代码 55 | 56 | Python Code 57 | 58 | ```py 59 | class Solution(object): 60 | def mySqrt(self, x): 61 | """ 62 | :type x: int 63 | :rtype: int 64 | """ 65 | l, r, m = 0, x, 0 66 | while l <= r: 67 | m = l + (r - l) // 2 68 | if m ** 2 == x: return m 69 | elif m ** 2 > x : r = m - 1 70 | else: l = m + 1 71 | return r 72 | ``` 73 | -------------------------------------------------------------------------------- /topics/day-63.md: -------------------------------------------------------------------------------- 1 | # 278. 第一个错误的版本 2 | 3 | https://leetcode-cn.com/problems/first-bad-version 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。 9 | 10 | 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。 11 | 12 | 你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 13 | 14 | 示例: 15 | 16 | 给定 n = 5,并且 version = 4 是第一个错误的版本。 17 | 18 | 调用 isBadVersion(3) -> false 19 | 调用 isBadVersion(5) -> true 20 | 调用 isBadVersion(4) -> true 21 | 22 | 所以,4 是第一个错误的版本。 23 | 24 | 来源:力扣(LeetCode) 25 | 链接:https://leetcode-cn.com/problems/first-bad-version 26 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 27 | ``` 28 | 29 | ## 思路 30 | 31 | **寻找最左边的满足条件的值** 32 | 33 | 框架: 34 | 35 | - 首先定义搜索区间为 [left, right],左右都闭合。 36 | - 循环搜索条件为 left <= right,只要区间内有元素就继续寻找。 37 | - 循环体内,我们不断更新 mid ,并判断 mid 是否符合题目要求。 38 | - 如果 mid 符合要求,我们找到了一个备胎, 接着收缩右边界,继续看看左边还有没有。 39 | - 否则收缩左边界,去右侧寻找。 40 | - 最后我们定点到 left 元素上,由于不会提前返回,因此我们需要检查最终的 left 是否符合要求。 41 | - 如果不符合题目要求,或者 left 出了右边的边界,说明没有找到,返回 -1。 42 | - 否则返回 left 即可。 43 | 44 | ## 复杂度 45 | 46 | - 时间复杂度:$O(log_n)$ 47 | - 空间复杂度:$O(1)$ 48 | 49 | ## 代码 50 | 51 | Python Code 52 | 53 | ```py 54 | # The isBadVersion API is already defined for you. 55 | # @param version, an integer 56 | # @return a bool 57 | # def isBadVersion(version): 58 | 59 | class Solution(object): 60 | def firstBadVersion(self, n): 61 | """ 62 | :type n: int 63 | :rtype: int 64 | """ 65 | l, r, m = 1, n, 0 66 | while l <= r: 67 | m = l + (r - l) // 2 68 | if isBadVersion(m): r = m - 1 69 | else: l = m + 1 70 | return l 71 | 72 | # 本题中“错误版本”一定存在,不然还是需要检查最终的左指针 73 | # return l if l <= n and isBadVersion(l) else -1 74 | ``` 75 | -------------------------------------------------------------------------------- /topics/day-64.md: -------------------------------------------------------------------------------- 1 | # 162. 寻找峰值 2 | 3 | https://leetcode-cn.com/problems/find-peak-element 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 峰值元素是指其值大于左右相邻值的元素。 9 | 10 | 给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。 11 | 12 | 数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。 13 | 14 | 你可以假设 nums[-1] = nums[n] = -∞。 15 | 16 | 示例 1: 17 | 18 | 输入: nums = [1,2,3,1] 19 | 输出: 2 20 | 解释: 3 是峰值元素,你的函数应该返回其索引 2。 21 | 示例 2: 22 | 23 | 输入: nums = [1,2,1,3,5,6,4] 24 | 输出: 1 或 5 25 | 解释: 你的函数可以返回索引 1,其峰值元素为 2; 26 | 或者返回索引 5, 其峰值元素为 6。 27 | 说明: 28 | 29 | 你的解法应该是 O(logN) 时间复杂度的。 30 | 31 | 来源:力扣(LeetCode) 32 | 链接:https://leetcode-cn.com/problems/find-peak-element 33 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 34 | ``` 35 | 36 | ## 代码 37 | 38 | ```js 39 | /** 40 | * @param {number[]} nums 41 | * @return {number} 42 | */ 43 | var findPeakElement = function (nums) { 44 | let l = 0, 45 | m = 0, 46 | r = nums.length - 1; 47 | while (l < r) { 48 | m = Math.floor(l + (r - l) / 2); 49 | if (nums[m] > nums[m + 1]) r = m; 50 | else l = m + 1; 51 | } 52 | return l; 53 | }; 54 | ``` 55 | 56 | ## 复杂度 57 | 58 | - 时间复杂度:$O(logn)$ 59 | - 空间复杂度:$O(1)$ 60 | -------------------------------------------------------------------------------- /topics/day-68.md: -------------------------------------------------------------------------------- 1 | # 1456. 定长子串中元音的最大数目 2 | 3 | https://leetcode-cn.com/problems/maximum-number-of-vowels-in-a-substring-of-given-length 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给你字符串 s 和整数 k 。 9 | 10 | 请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。 11 | 12 | 英文中的 元音字母 为(a, e, i, o, u)。 13 | 14 | 15 | 16 | 示例 1: 17 | 18 | 输入:s = "abciiidef", k = 3 19 | 输出:3 20 | 解释:子字符串 "iii" 包含 3 个元音字母。 21 | 示例 2: 22 | 23 | 输入:s = "aeiou", k = 2 24 | 输出:2 25 | 解释:任意长度为 2 的子字符串都包含 2 个元音字母。 26 | 示例 3: 27 | 28 | 输入:s = "leetcode", k = 3 29 | 输出:2 30 | 解释:"lee"、"eet" 和 "ode" 都包含 2 个元音字母。 31 | 示例 4: 32 | 33 | 输入:s = "rhythms", k = 4 34 | 输出:0 35 | 解释:字符串 s 中不含任何元音字母。 36 | 示例 5: 37 | 38 | 输入:s = "tryhard", k = 4 39 | 输出:1 40 | 41 | 42 | 提示: 43 | 44 | 1 <= s.length <= 10^5 45 | s 由小写英文字母组成 46 | 1 <= k <= s.length 47 | 48 | 来源:力扣(LeetCode) 49 | 链接:https://leetcode-cn.com/problems/maximum-number-of-vowels-in-a-substring-of-given-length 50 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 51 | ``` 52 | 53 | ## 思路 54 | 55 | 看到题目第一个想法就是滑动窗口了,因为子字符串的长度 k 是固定的,所以真的很简单。 56 | 57 | - 使用一个长度为 k 的窗口从 s 的左侧滑向右侧 58 | - 分别计算窗口中包含的元音个数,最终返回最大值 59 | 60 | 要点是如何计算窗口中的元音个数?如果窗口每移动一步就遍历计算一次,必然是超时的。 61 | 62 | 其实仔细想想,窗口每次移动时,其中包含的元素只有两个变化: 63 | 64 | 1. 窗口左侧丢弃了一个元素 65 | 2. 窗口右侧新增了一个元素 66 | 67 | 那这样我们只需要在滑动开始时记录一下窗口中的元音个数 `count`,之后移动窗口时判断左侧丢弃的元素和右侧新增的元素是不是元音,对应地减少或者增加 `count` 就行。 68 | 69 | ![temp.png](https://pic.leetcode-cn.com/938297f94d75b5408e096e49ad7d3f28d9da48dbbd962abb33b8c6078af3dea5-temp.png) 70 | 71 | ## 复杂度 72 | 73 | - 时间复杂度:$O(N)$,N 为字符串的长度。 74 | - 空间复杂度:$O(1)$。 75 | 76 | ## 代码 77 | 78 | ```javascript 79 | /** 80 | * @param {string} s 81 | * @param {number} k 82 | * @return {number} 83 | */ 84 | var maxVowels = function (s, k) { 85 | const vowels = new Set(['a', 'e', 'i', 'o', 'u']); 86 | let count = 0, 87 | l = 0, 88 | r = 0; 89 | while (r < k) { 90 | vowels.has(s[r]) && count++; 91 | r++; 92 | } 93 | let max = count; 94 | while (r < s.length) { 95 | vowels.has(s[r]) && count++; 96 | vowels.has(s[l]) && count--; 97 | l++; 98 | r++; 99 | max = Math.max(max, count); 100 | } 101 | return max; 102 | }; 103 | ``` 104 | -------------------------------------------------------------------------------- /topics/day-69.md: -------------------------------------------------------------------------------- 1 | # 438. 找到字符串中所有字母异位词 2 | 3 | https://leetcode-cn.com/problems/find-all-anagrams-in-a-string 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。 9 | 10 | 字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。 11 | 12 | 说明: 13 | 14 | 字母异位词指字母相同,但排列不同的字符串。 15 | 不考虑答案输出的顺序。 16 | 示例 1: 17 | 18 | 输入: 19 | s: "cbaebabacd" p: "abc" 20 | 21 | 输出: 22 | [0, 6] 23 | 24 | 解释: 25 | 起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。 26 | 起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。 27 | 示例 2: 28 | 29 | 输入: 30 | s: "abab" p: "ab" 31 | 32 | 输出: 33 | [0, 1, 2] 34 | 35 | 解释: 36 | 起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。 37 | 起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。 38 | 起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。 39 | 40 | 来源:力扣(LeetCode) 41 | 链接:https://leetcode-cn.com/problems/find-all-anagrams-in-a-string 42 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 43 | ``` 44 | 45 | ## 思路 46 | 47 | 看完题目思路也很清晰了,就是用定宽的滑动窗口;而字母异位词,用大白话来说就是两个字符串包含的各种字母数量都相等。 48 | 49 | 接着第二个问题是,用什么方式来记录窗口中各个字母出现的次数? 50 | 51 | 哈希表是比较一个比较符合直觉的想法。 52 | 53 | 还有一种就是用数组。因为字符串中只包含小写字母,也就是只有 26 种字母,所以我们可以使用一个长度为 26 的数组来记录,数组下标表示字母,值则表示字母出现的次数。 54 | 55 | ## 复杂度 56 | 57 | - 时间复杂度:$O(n)$,n 为字符串 s 的长度,使用滑动窗口只需要遍历一次 s,而比较窗口中字母数量和字符串 p 中字母数量的操作,虽然也用到了循环,但最多也就是 26 次,所以最终的时间复杂度还是 $O(n)$。 58 | - 空间复杂度:$O(1)$,用到了两个长度为 26 的数组来记录字母出现次数以及若干个指针,但这些都与输入的字符串长度规模无关,是常数级别的空间,所以最终的空间复杂度还是 $O(1)$。 59 | 60 | ## 代码 61 | 62 | Python Code 63 | 64 | ```py 65 | class Solution(object): 66 | def isSame(self, a, b): 67 | for i in range(0, len(a)): 68 | if a[i] != b[i]: return False 69 | return True 70 | 71 | def findAnagrams(self, s, p): 72 | """ 73 | :type s: str 74 | :type p: str 75 | :rtype: List[int] 76 | """ 77 | if not s or not p or len(s) < len(p): return [] 78 | 79 | a = ord('a') 80 | # 计算字符串 p 中的字母数量 81 | countP = [0 for _ in range(26)] 82 | for c in p: 83 | countP[ord(c) - a] += 1 84 | 85 | # 在 s 中先滑出一个窗口,计算窗口中的字母数量 86 | countS = [0 for _ in range(26)] 87 | for k in range(0, len(p)): 88 | countS[ord(s[k]) - a] += 1 89 | 90 | ans = [] 91 | # 比较窗口中的字母数量和字符串 p 中的字母数量 92 | if (self.isSame(countP, countS)): 93 | ans.append(0) 94 | 95 | # 继续向右滑动窗口,更新 -> 比较 96 | i, j = 0, len(p) - 1 97 | while j < len(s) - 1: 98 | countS[ord(s[i]) - a] -= 1 99 | i += 1 100 | j += 1 101 | countS[ord(s[j]) - a] += 1 102 | if (self.isSame(countP, countS)): 103 | ans.append(i) 104 | return ans 105 | ``` 106 | -------------------------------------------------------------------------------- /topics/day-74.md: -------------------------------------------------------------------------------- 1 | # 1254. 统计封闭岛屿的数目 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。 7 | 8 | 我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其称为一座「岛屿」。 9 | 10 | 如果一座岛屿 完全 由水域包围,即陆地边缘上下左右所有相邻区域都是水域,那么我们将其称为 「封闭岛屿」。 11 | 12 | 请返回封闭岛屿的数目。 13 | 14 | 输入:grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]] 15 | 输出:2 16 | 解释: 17 | 灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。 18 | 输入:grid = [[0,0,1,0,0],[0,1,0,1,0],[0,1,1,1,0]] 19 | 输出:1 20 | 输入:grid = [[1,1,1,1,1,1,1], 21 | [1,0,0,0,0,0,1], 22 | [1,0,1,1,1,0,1], 23 | [1,0,1,0,1,0,1], 24 | [1,0,1,1,1,0,1], 25 | [1,0,0,0,0,0,1], 26 | [1,1,1,1,1,1,1]] 27 | 输出:2 28 | ``` 29 | 30 | ## 思路 31 | 32 | 先把跟边界连通的 0 变成 1 (或者其他占位符),然后计算其他连通的 0 有多少组。 33 | 34 | ## 复杂度 35 | 36 | - 时间复杂度:$O(m*n)$,m 和 n 是 grid 的长宽。 37 | - 空间复杂度:$O(max(m, n))$,递归栈的空间我感觉是这个。 38 | 39 | ## 代码 40 | 41 | JavaScript Code 42 | 43 | ```js 44 | /** 45 | * @param {number[][]} grid 46 | * @return {number} 47 | */ 48 | var closedIsland = function (grid) { 49 | const outOfBoundary = (grid, x, y) => 50 | x < 0 || x >= grid.length || y < 0 || y >= grid[0].length; 51 | 52 | const dfs = (grid, x, y) => { 53 | if (outOfBoundary(grid, x, y)) return false; 54 | if (grid[x][y] === 1) return true; 55 | grid[x][y] = 1; 56 | 57 | if ( 58 | dfs(grid, x - 1, y) && 59 | dfs(grid, x + 1, y) && 60 | dfs(grid, x, y - 1) && 61 | dfs(grid, x, y + 1) 62 | ) 63 | return true; 64 | 65 | return false; 66 | }; 67 | 68 | const mark = (grid, x, y) => { 69 | if (outOfBoundary(grid, x, y) || grid[x][y] === 1) return; 70 | 71 | grid[x][y] = 1; 72 | mark(grid, x - 1, y); 73 | mark(grid, x + 1, y); 74 | mark(grid, x, y - 1); 75 | mark(grid, x, y + 1); 76 | }; 77 | 78 | // 将连通边界的 0 都改成 1 79 | for (let i = 0; i < grid.length; i++) { 80 | mark(grid, i, 0); 81 | mark(grid, i, grid[0].length - 1); 82 | } 83 | for (let j = 0; j < grid[0].length; j++) { 84 | mark(grid, 0, j); 85 | mark(grid, grid.length - 1, j); 86 | } 87 | 88 | let ans = 0; 89 | 90 | for (let i = 0; i < grid.length; i++) { 91 | for (let j = 0; j < grid[0].length; j++) { 92 | if (grid[i][j] === 1) continue; 93 | if (dfs(grid, i, j)) ans++; 94 | } 95 | } 96 | return ans; 97 | }; 98 | ``` 99 | 100 | _Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/101#issuecomment-673413128_ 101 | -------------------------------------------------------------------------------- /topics/day-79.md: -------------------------------------------------------------------------------- 1 | # 980.不同路径 III 2 | 3 | https://leetcode-cn.com/problems/unique-paths-iii 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 在二维网格 grid 上,有 4 种类型的方格: 9 | 10 | 1 表示起始方格。且只有一个起始方格。 11 | 2 表示结束方格,且只有一个结束方格。 12 | 0 表示我们可以走过的空方格。 13 | -1 表示我们无法跨越的障碍。 14 | 返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目。 15 | 16 | 每一个无障碍方格都要通过一次,但是一条路径中不能重复通过同一个方格。 17 | 18 | 19 | 20 | 示例 1: 21 | 22 | 输入:[[1,0,0,0],[0,0,0,0],[0,0,2,-1]] 23 | 输出:2 24 | 解释:我们有以下两条路径: 25 | 1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2) 26 | 2. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2) 27 | 示例 2: 28 | 29 | 输入:[[1,0,0,0],[0,0,0,0],[0,0,0,2]] 30 | 输出:4 31 | 解释:我们有以下四条路径: 32 | 1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2),(2,3) 33 | 2. (0,0),(0,1),(1,1),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,3),(1,3),(2,3) 34 | 3. (0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(1,1),(0,1),(0,2),(0,3),(1,3),(2,3) 35 | 4. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2),(2,3) 36 | 示例 3: 37 | 38 | 输入:[[0,1],[2,0]] 39 | 输出:0 40 | 解释: 41 | 没有一条路能完全穿过每一个空的方格一次。 42 | 请注意,起始和结束方格可以位于网格中的任意位置。 43 | 44 | 45 | 提示: 46 | 47 | 1 <= grid.length * grid[0].length <= 20 48 | 来源:力扣(LeetCode) 49 | 链接:https://leetcode-cn.com/problems/unique-paths-iii 50 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 51 | ``` 52 | 53 | ## 思路 54 | 55 | 从起始格子开始,尝试每一个 0 空格。当走到 2 时,如果此时网格没有还没走过的空格,说明这是一条可行的路径。也就是说我们需要用一个方式来标志已经走过的空格,可以把格子设为 -1,回溯时需要把格子重新设置为 0,不影响其他路径的尝试。 56 | 57 | 当我们走到 2 时,如何判断网格中是否还有未走过的空格? 58 | 59 | 每次都去遍历整个网格的话,时间复杂度太高。我们可以在开始先统计网格中一共有多少个可以走的格子,每走过一个格子计数器就减一。 60 | 61 | ## 复杂度 62 | 63 | - 时间复杂度:$O(4^{m*n})$, m, n 分别是网格的长宽。找到起始格子和统计空格用了 $O(m*n)$,递归的时间复杂度 $O(4^{m*n})$,网格一共有 $m*n$ 个格子,每个格子有 4 个方向可以走。 64 | - 空间复杂度:递归栈的最大空间 $O(m*n)$。 65 | 66 | > p.s. 下方代码是我看错题了,求了所有路径。实际上只需要一个计数器来记录路径数,不消耗额外空间。 67 | 68 | ## 代码 69 | 70 | JavaScript Code 71 | 72 | ```js 73 | /** 74 | * @param {number[][]} grid 75 | * @return {number} 76 | */ 77 | var uniquePathsIII = function (grid) { 78 | const offsets = [ 79 | [-1, 0], 80 | [1, 0], 81 | [0, -1], 82 | [0, 1], 83 | ]; 84 | const ans = []; 85 | const dfs = (grid, x, y, spaceCnt, path) => { 86 | if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length) return; 87 | 88 | if (grid[x][y] === 2) { 89 | spaceCnt === 0 && ans.push([...path]); 90 | return; 91 | } 92 | 93 | if (grid[x][y] === -1) return; 94 | grid[x][y] = -1; // mark 95 | 96 | // recursion 97 | for (const [ox, oy] of offsets) { 98 | // p.s. 如果 (x+ox, y+oy) 不在网格中或者是障碍的话,也可以提前剪枝。 99 | dfs(grid, x + ox, y + oy, spaceCnt - 1, [...path, [x, y]]); 100 | } 101 | grid[x][y] = 0; // backtrack 102 | }; 103 | 104 | let startPos = {}; 105 | const init = grid => { 106 | let spaceCnt = 1; // 起始方格也是要走的一个格子 107 | for (let x = 0; x < grid.length; x++) { 108 | for (let y = 0; y < grid[x].length; y++) { 109 | if (grid[x][y] === 1) startPos = { x, y }; 110 | if (grid[x][y] === 0) spaceCnt++; 111 | } 112 | } 113 | return spaceCnt; 114 | }; 115 | 116 | // 统计要走的格子总数 117 | const spaceCnt = init(grid); 118 | dfs(grid, startPos.x, startPos.y, spaceCnt, []); 119 | return ans.length; 120 | }; 121 | ``` 122 | 123 | _Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/106#issuecomment-676368122_ 124 | -------------------------------------------------------------------------------- /topics/day-80.md: -------------------------------------------------------------------------------- 1 | # 322. 零钱兑换 2 | 3 | https://leetcode-cn.com/problems/coin-change 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 9 | 10 | 11 | 12 | 示例 1: 13 | 14 | 输入: coins = [1, 2, 5], amount = 11 15 | 输出: 3 16 | 解释: 11 = 5 + 5 + 1 17 | 示例 2: 18 | 19 | 输入: coins = [2], amount = 3 20 | 输出: -1 21 | 22 | 23 | 说明: 24 | 你可以认为每种硬币的数量是无限的。 25 | 26 | 来源:力扣(LeetCode) 27 | 链接:https://leetcode-cn.com/problems/coin-change 28 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 29 | ``` 30 | 31 | ## 思路 32 | 33 | 以输入 `coins = [1, 2, 5], amount = 11` 为例, 34 | 35 | 假设我们先选了第一枚硬币 `1`,那子问题就变成了 `coins = [1, 2, 5], amount = (11 - 1)`,我们只要求出这个子问题的最优解,也就能求出原题的最优解。 36 | 37 | 但假如我们是先选了第二枚硬币,那子问题就变成了 `coins = [1, 2, 5], amount = (11 - 2)`。 38 | 39 | 由于我们并不确定先选哪枚硬币可以得到最优解,所以需要每枚硬币都试一次。 40 | 41 | ```js 42 | for (const coin of coins) { 43 | // 假设我们先拿了面值为 coin 的硬币, 44 | // 接下来求总金额为 amount - coin 时问题的最优解 45 | } 46 | ``` 47 | 48 | 另外我们用一个一维数组 dp 来记录问题的解,dp[i] 表示**总金额为 i 时兑换零钱所需最少硬币个数**。 49 | 50 | 前面已经提到过子问题是什么了。当总金额为 i 的时候,如果我们选了面值为 coin 的硬币,那问题就变成了求总金额为 i - coin 时的最优解,得到这个解再加上 1 (当前选的这枚硬币)就是总金额为 i 时问题的最优解。 51 | 52 | ```js 53 | dp[i] = dp[i - coin] + 1; 54 | ``` 55 | 56 | 如果不选眼前这枚硬币,那就更简单了,要兑换零钱的总金额还是 i。 57 | 58 | ```js 59 | dp[i] = dp[i]; 60 | ``` 61 | 62 | 因为是求最优解,所以我们也要从这两种情况中选择最优解。 63 | 64 | ```js 65 | dp[i] = min(dp[i - coin] + 1, dp[i]); 66 | ``` 67 | 68 | 因为总金额为零时不需要零钱,所以 `dp[0] = 0`,接着我们就可以自底向上地填充 dp 数组了。 69 | 70 | ## 复杂度 71 | 72 | - 时间复杂度:$O(coins*amount)$,coins 是硬币种数。 73 | - 空间复杂度:$O(amount)$。 74 | 75 | ## 代码 76 | 77 | JavaScript Code 78 | 79 | ```js 80 | /** 81 | * @param {number[]} coins 82 | * @param {number} amount 83 | * @return {number} 84 | */ 85 | var coinChange = function (coins, amount) { 86 | const dp = Array(amount + 1).fill(Infinity); 87 | dp[0] = 0; 88 | for (const coin of coins) { 89 | for (let i = coin; i <= amount; i++) { 90 | dp[i] = Math.min(dp[i], dp[i - coin] + 1); 91 | } 92 | } 93 | return dp[amount] === Infinity ? -1 : dp[amount]; 94 | }; 95 | ``` 96 | 97 | _Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/107#issuecomment-676260687_ 98 | -------------------------------------------------------------------------------- /topics/day-81.md: -------------------------------------------------------------------------------- 1 | # 416.分割等和子集 2 | 3 | https://leetcode-cn.com/problems/partition-equal-subset-sum 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 9 | 10 | 注意: 11 | 12 | 每个数组中的元素不会超过 100 13 | 数组的大小不会超过 200 14 | 示例 1: 15 | 16 | 输入: [1, 5, 11, 5] 17 | 18 | 输出: true 19 | 20 | 解释: 数组可以分割成 [1, 5, 5] 和 [11]. 21 | 22 | 23 | 示例 2: 24 | 25 | 输入: [1, 2, 3, 5] 26 | 27 | 输出: false 28 | 29 | 解释: 数组不能分割成两个元素和相等的子集. 30 | 31 | 来源:力扣(LeetCode) 32 | 链接:https://leetcode-cn.com/problems/partition-equal-subset-sum 33 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 34 | ``` 35 | 36 | ## 思路 1: DP 37 | 38 | **题目理解** 39 | 40 | 一开始看到题目提到两个子数组,还有点不知道怎么跟 DP 扯上关系。 41 | 42 | 但其实题目可以换一个说法:**给定数组 nums,是否存在一个子数组,该子数组的和等于 nums 元素和的一半**。 43 | 44 | 这样就清晰多了。 45 | 46 | **解题** 47 | 48 | 我们还是用一个一维数组 dp 来记录题目的解,dp[i] 表示**是否存在元素和为 i 的子数组**。 49 | 50 | 对于 nums 中的每个数字 n 来说,都有**选**和**不选**两种选择,选的话问题变成 dp[i - n],不选的话问题还是 dp[i],所以: 51 | 52 | ``` 53 | dp[i] = dp[i - n] or dp[i] 54 | ``` 55 | 56 | ### 复杂度 57 | 58 | - 时间复杂度:$O(len*target)$, len 是 nums 的长度,target 是 nums 元素和的一半。 59 | - 空间复杂度:$O(target)$, target 是 nums 数组元素和的一半。 60 | 61 | ### 代码 62 | 63 | JavaScript Code 64 | 65 | ```js 66 | /** 67 | * @param {number[]} nums 68 | * @return {boolean} 69 | */ 70 | var canPartition = function (nums) { 71 | const target = nums.reduce((a, b) => a + b) / 2; 72 | if (target % 1 !== 0) return false; // nums 和为奇数 73 | 74 | const dp = Array(target + 1).fill(false); 75 | dp[0] = true; 76 | for (const n of nums) { 77 | for (let i = target; i >= n; i--) { 78 | // 逆向填充 79 | if (dp[target]) return true; // 提前返回结果 80 | dp[i] = dp[i] || dp[i - n]; 81 | } 82 | } 83 | return dp[target]; 84 | }; 85 | ``` 86 | 87 | ## 思路 2: DFS 88 | 89 | 将两个子数组 a 和 b 分别初始化为 nums 和 [],然后不断从 a 中取出数字放到 b 中,当两个子数组的和相等时,返回 true。 90 | 91 | 对于 a 中的每个数字,都有**选**与**不选**两种选择。 92 | 93 | p.s. 要使用记忆化递归才不会超时 94 | 95 | ### 复杂度 96 | 97 | - 时间复杂度:$O(2^n)$,n 为数组 nums 长度,可以看成是二叉树的遍历。 98 | - 空间复杂度:$O(logn)$,n 为数组 nums 长度,递归栈消耗的空间。 99 | 100 | ### 代码 101 | 102 | Python Code 103 | 104 | ```py 105 | class Solution(object): 106 | def canPartition(self, nums): 107 | """ 108 | :type nums: List[int] 109 | :rtype: bool 110 | """ 111 | @lru_cache 112 | def dfs(i, sum1, sum2): 113 | if sum1 == sum2: return True 114 | if sum2 > sum1 or i >= len(nums): return False 115 | return dfs(i + 1, sum1 - nums[i], sum2 + nums[i]) or dfs(i + 1, sum1, sum2) 116 | 117 | total = sum(nums) 118 | if total % 2 == 1: return False 119 | return dfs(0, total, 0) 120 | ``` 121 | 122 | _Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/108#issuecomment-677698946_ 123 | -------------------------------------------------------------------------------- /topics/day-82.md: -------------------------------------------------------------------------------- 1 | # 494. 目标和 2 | 3 | https://leetcode-cn.com/problems/target-sum 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。 9 | 10 | 返回可以使最终数组和为目标数 S 的所有添加符号的方法数。 11 | 12 | 13 | 14 | 示例: 15 | 16 | 输入:nums: [1, 1, 1, 1, 1], S: 3 17 | 输出:5 18 | 解释: 19 | 20 | -1+1+1+1+1 = 3 21 | +1-1+1+1+1 = 3 22 | +1+1-1+1+1 = 3 23 | +1+1+1-1+1 = 3 24 | +1+1+1+1-1 = 3 25 | 26 | 一共有5种方法让最终目标和为3。 27 | 28 | 29 | 提示: 30 | 31 | 数组非空,且长度不会超过 20 。 32 | 初始的数组的和不会超过 1000 。 33 | 保证返回的最终结果能被 32 位整数存下。 34 | 35 | 来源:力扣(LeetCode) 36 | 链接:https://leetcode-cn.com/problems/target-sum 37 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 38 | ``` 39 | 40 | ## 思路 1: DP 41 | 42 | 简单理解一下题目就是,我们要从数组中选出一个正数集,然后剩下的数字自动变成了一个负数集,这两个集合的和要刚好等于目标数 S。 43 | 44 | 换句话说,我们要从原数组中选出一个子集,满足元素的和为 target(这个 target 不是原题中的 S),只要确定这个 target,剩下就是 0-1 背包问题的套路了。 45 | 46 | 已知: 47 | 48 | - 正数集 + 负数集 = S 49 | - 正数集 - 负数集 = sum 50 | 51 | sum 是原数组的和。 52 | 53 | 可得: 54 | 55 | - 正数集 = (S + sum) / 2 56 | 57 | 所以 `(S + sum) / 2` 就是我们要找的 target。 58 | 59 | ### 复杂度 60 | 61 | - 时间复杂度:$O(len*(sum+S)/2)$,len 是数组长度,sum 是数组元素和,S 是目标数。 62 | - 空间复杂度:$O((sum+S)/2)$。 63 | 64 | ### 代码 65 | 66 | JavaScript Code 67 | 68 | ```js 69 | /** 70 | * @param {number[]} nums 71 | * @param {number} S 72 | * @return {number} 73 | */ 74 | var findTargetSumWays = function (nums, S) { 75 | const sum = nums.reduce((a, b) => a + b, 0); 76 | if (sum < S) return 0; 77 | 78 | const sumOfPositives = (sum + S) / 2; 79 | if (sumOfPositives % 1 !== 0) return 0; 80 | 81 | const dp = Array(sumOfPositives + 1).fill(0); 82 | // target 为 0 时,正数集为空 83 | // 也就是只有给所有数字都加上 - 号这一种方法 84 | dp[0] = 1; 85 | for (const n of nums) { 86 | for (let i = sumOfPositives; i >= n; i--) { 87 | dp[i] = dp[i] + dp[i - n]; 88 | } 89 | } 90 | return dp[sumOfPositives]; 91 | }; 92 | ``` 93 | 94 | ## 思路 2: DFS 95 | 96 | DFS 枚举所有排列组合,计算组合的和,如果满足和等于 S 则结果++。 97 | 98 | ### 复杂度 99 | 100 | - 时间复杂度:$O(2^n)$,n 是数组长度。 101 | - 空间复杂度:$O(logn)$。 102 | 103 | ### 代码 104 | 105 | JavaScript Code 106 | 107 | ```js 108 | /** 109 | * @param {number[]} nums 110 | * @param {number} S 111 | * @return {number} 112 | */ 113 | var findTargetSumWays = function (nums, S) { 114 | const dfs = (nums, i, sum) => { 115 | if (sum === S && i === nums.length) return 1; 116 | if (i > nums.length) return 0; 117 | return ( 118 | dfs(nums, i + 1, sum + nums[i]) + dfs(nums, i + 1, sum - nums[i]) 119 | ); 120 | }; 121 | return dfs(nums, 0, 0); 122 | }; 123 | ``` 124 | 125 | _Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/109#issuecomment-678378367_ 126 | -------------------------------------------------------------------------------- /topics/day-83.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 7 | ``` 8 | -------------------------------------------------------------------------------- /topics/day-84.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 7 | ``` 8 | -------------------------------------------------------------------------------- /topics/day-85.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 7 | ``` 8 | -------------------------------------------------------------------------------- /topics/day-86.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 7 | ``` 8 | -------------------------------------------------------------------------------- /topics/day-87.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 7 | ``` 8 | -------------------------------------------------------------------------------- /topics/day-88.md: -------------------------------------------------------------------------------- 1 | - [95. 不同的二叉搜索树 II](#95.不同的二叉搜索树II) 2 | - [96. 不同的二叉搜索树](#96.不同的二叉搜索树) 3 | 4 | # 95.不同的二叉搜索树 II 5 | 6 | https://leetcode-cn.com/problems/unique-binary-search-trees-ii/ 7 | 8 | ## 思路 9 | 10 | 先从 [1, n] 中选出一个数字 i 作为根元素,然后用剩下的数字去分别构建左右子树: 11 | 12 | - [1, i - 1] 的数字用于构建左子树 13 | - [i + 1, n] 的数字用于构建右子树 14 | 15 | 可以看出构建左右子树其实就是原题的子问题,只是规模减小了,我们可以再对子问题分别进行拆分,直到不能再拆,也就是没有数字了,这时候直接返回 null 即可。 16 | 17 | 假设我们已经实现了函数 `generateTrees`,`generateTrees([1, n])` 会返回 N 棵二叉搜索树,那么当我们选定一个数字 i 作为根节点时,问题可以拆分成: 18 | 19 | - `generateTrees([1, i - 1])` ,返回 L 棵左子树, 20 | - `generateTrees([i + 1, n])` ,返回 R 棵右子树, 21 | 22 | 那原问题的答案也很容易得出,就是将所有左子树和右子树进行排列组合,最终得到 I 棵二叉搜索树。 23 | 24 | 但 [1, n] 中任意一个数字都可以作为根节点,所以还要加一层循环。 25 | 26 | ## 复杂度 27 | 28 | - 时间复杂度: 29 | - 空间复杂度: 30 | 31 | ## 代码 32 | 33 | JavaScript Code 34 | 35 | ```js 36 | /** 37 | * Definition for a binary tree node. 38 | * function TreeNode(val, left, right) { 39 | * this.val = (val===undefined ? 0 : val) 40 | * this.left = (left===undefined ? null : left) 41 | * this.right = (right===undefined ? null : right) 42 | * } 43 | */ 44 | /** 45 | * @param {number} n 46 | * @return {TreeNode[]} 47 | */ 48 | var generateTrees = function (n) { 49 | if (n === 0) return []; 50 | const dfs = ([start, end]) => { 51 | if (start > end) return [null]; 52 | const res = []; 53 | for (let i = start; i <= end; i++) { 54 | // 用所有比 i 小的数字去建左子树,可以建 leftTrees 个左子树 55 | const leftTrees = dfs([start, i - 1]); 56 | // 用所有比 i 大的数字去建右子树,可以建 leftTrees 个右子树 57 | const rightTrees = dfs([i + 1, end]); 58 | for (const left of leftTrees) { 59 | for (const right of rightTrees) { 60 | // i 作为父节点,枚举所有左子树、右子树的排列组合 61 | const root = new TreeNode(i, left, right); 62 | res.push(root); 63 | } 64 | } 65 | } 66 | return res; 67 | }; 68 | return dfs([1, n]); 69 | }; 70 | ``` 71 | 72 | # 96.不同的二叉搜索树 73 | 74 | https://leetcode-cn.com/problems/unique-binary-search-trees/ 75 | 76 | ## 思路 77 | 78 | 大概思路和 95 题差不多,不过要用记忆化递归才不会超时。 79 | 80 | ## 代码 81 | 82 | Python Code 83 | 84 | ```py 85 | class Solution: 86 | def numTrees(self, n): 87 | @lru_cache 88 | def dfs(start, end): 89 | if end <= start: return 1 90 | cnt = 0 91 | for i in range(start, end + 1): 92 | left = dfs(start, i - 1) 93 | right = dfs(i + 1, end) 94 | cnt += left * right 95 | return cnt 96 | return dfs(1, n) 97 | ``` 98 | 99 | _Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/115#issuecomment-682328630_ 100 | -------------------------------------------------------------------------------- /topics/day-89.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 7 | ``` 8 | -------------------------------------------------------------------------------- /topics/day-90.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 7 | ``` 8 | -------------------------------------------------------------------------------- /topics/day-91.md: -------------------------------------------------------------------------------- 1 | # 881. 救生艇 2 | 3 | ## 题目描述 4 | 5 | ``` 6 | 第 i 个人的体重为 people[i],每艘船可以承载的最大重量为 limit。 7 | 8 | 每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。 9 | 10 | 返回载到每一个人所需的最小船数。(保证每个人都能被船载)。 11 | 12 | 13 | 14 | 示例 1: 15 | 16 | 输入:people = [1,2], limit = 3 17 | 输出:1 18 | 解释:1 艘船载 (1, 2) 19 | 示例 2: 20 | 21 | 输入:people = [3,2,2,1], limit = 3 22 | 输出:3 23 | 解释:3 艘船分别载 (1, 2), (2) 和 (3) 24 | 示例 3: 25 | 26 | 输入:people = [3,5,3,4], limit = 5 27 | 输出:4 28 | 解释:4 艘船分别载 (3), (3), (4), (5) 29 | 提示: 30 | 31 | 1 <= people.length <= 50000 32 | 1 <= people[i] <= limit <= 30000 33 | ``` 34 | 35 | ## 思路 36 | 37 | - 先对所有人按体重从轻到重排序。 38 | - 使用双指针从头尾同时遍历: 39 | - 如果较重的较轻的两个人可以坐同一艘船走,就一起走,boat++; 40 | - 如果不能一起走,那么较重的那个人自己坐一艘船走,boat++; 41 | 42 | ## 复杂度 43 | 44 | - 时间复杂度:$O(nlogn)$,n 为总人数。 45 | - 空间复杂度:$O(1)$。 46 | 47 | ## 代码 48 | 49 | JavaScript Code 50 | 51 | ```js 52 | /** 53 | * @param {number[]} people 54 | * @param {number} limit 55 | * @return {number} 56 | */ 57 | var numRescueBoats = function (people, limit) { 58 | people.sort((a, b) => a - b); 59 | let l = 0, 60 | r = people.length - 1, 61 | ans = 0; 62 | while (l <= r) { 63 | // 最重的和最轻的一起走 64 | if (people[r] + people[l] <= limit) l++; 65 | // 最重的一个人走 66 | r--; 67 | ans++; 68 | } 69 | return ans; 70 | }; 71 | ``` 72 | 73 | _Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/118#issuecomment-683506861_ 74 | -------------------------------------------------------------------------------- /topics/greedy/ext-assign-cookies.md: -------------------------------------------------------------------------------- 1 | # 455. 分发饼干 2 | 3 | https://leetcode-cn.com/problems/assign-cookies/ 4 | 5 | - [455. 分发饼干](#455-分发饼干) 6 | - [题目描述](#题目描述) 7 | - [方法 1:贪心算法](#方法-1贪心算法) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码(JS/Python)](#代码jspython) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 16 | 17 | 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 18 | 19 |   20 | 示例 1: 21 | 22 | 输入: g = [1,2,3], s = [1,1] 23 | 输出: 1 24 | 解释: 25 | 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 26 | 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 27 | 所以你应该输出1。 28 | 示例 2: 29 | 30 | 输入: g = [1,2], s = [1,2,3] 31 | 输出: 2 32 | 解释: 33 | 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 34 | 你拥有的饼干数量和尺寸都足以让所有孩子满足。 35 | 所以你应该输出2. 36 |   37 | 38 | 提示: 39 | 40 | 1 <= g.length <= 3 * 104 41 | 0 <= s.length <= 3 * 104 42 | 1 <= g[i], s[j] <= 231 - 1 43 | 44 | 来源:力扣(LeetCode) 45 | 链接:https://leetcode-cn.com/problems/assign-cookies 46 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 47 | ``` 48 | 49 | ## 方法 1:贪心算法 50 | 51 | ### 思路 52 | 53 | 我们的目标是让更多的孩子吃饱。 54 | 55 | 而饥饿值小的孩子更容易吃饱,所以我们考虑先让饥饿值最小的孩子吃饼干。 56 | 57 | 而为了消耗更少的饼干,当然是在能让这个孩子吃饱的饼干里选最小的那块给他吃。 58 | 59 | 这个孩子吃饱后,我们用同样的策略,在剩下的孩子中选出饥饿值最小的孩子,分给他最小的能吃饱的饼干。重复这个策略,直到消耗完所有满足条件的饼干,或者所有孩子都吃饱了。 60 | 61 | 至于具体的实现,我们需要获取孩子饥饿值和饼干大小的关系,所以需要排序。 62 | 63 | ### 复杂度分析 64 | 65 | - 时间复杂度:$O(nlogn+mlogm)$,n 是 g.length,m 是 s.length,排序的时间。 66 | - 空间复杂度:$O(1)$。 67 | 68 | ### 代码(JS/Python) 69 | 70 | JavaScript Code 71 | 72 | ```js 73 | /** 74 | * @param {number[]} g 75 | * @param {number[]} s 76 | * @return {number} 77 | */ 78 | var findContentChildren = function (g, s) { 79 | g.sort(asc); 80 | s.sort(asc); 81 | 82 | let gp = 0, 83 | sp = 0; 84 | 85 | while (sp < s.length && gp < g.length) { 86 | // 发现满足条件的饼干,喂饱一个孩子 87 | if (s[sp] >= g[gp]) gp++; 88 | // 继续找下一块饼干 89 | sp++; 90 | } 91 | return gp; 92 | 93 | // ************************ 94 | function asc(a, b) { 95 | return a - b; 96 | } 97 | }; 98 | ``` 99 | 100 | Python Code 101 | 102 | ```py 103 | class Solution(object): 104 | def findContentChildren(self, g, s): 105 | """ 106 | :type g: List[int] 107 | :type s: List[int] 108 | :rtype: int 109 | """ 110 | g.sort() 111 | s.sort() 112 | 113 | i, j = 0, 0 114 | 115 | while i < len(g) and j < len(s): 116 | if s[j] >= g[i]: 117 | i += 1 118 | j += 1 119 | 120 | return i 121 | ``` 122 | -------------------------------------------------------------------------------- /topics/greedy/ext-can-place-flowers.md: -------------------------------------------------------------------------------- 1 | # 605. 种花问题 2 | 3 | https://leetcode-cn.com/problems/can-place-flowers/ 4 | 5 | - [605. 种花问题](#605-种花问题) 6 | - [题目描述](#题目描述) 7 | - [方法 1: 贪心](#方法-1-贪心) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。 16 | 17 | 给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。 18 | 19 | 示例 1: 20 | 21 | 输入: flowerbed = [1,0,0,0,1], n = 1 22 | 输出: True 23 | 示例 2: 24 | 25 | 输入: flowerbed = [1,0,0,0,1], n = 2 26 | 输出: False 27 | 注意: 28 | 29 | 数组内已种好的花不会违反种植规则。 30 | 输入的数组长度范围为 [1, 20000]。 31 | n 是非负整数,且不会超过输入数组的大小。 32 | 33 | 来源:力扣(LeetCode) 34 | 链接:https://leetcode-cn.com/problems/can-place-flowers 35 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 36 | ``` 37 | 38 | ## 方法 1: 贪心 39 | 40 | ### 思路 41 | 42 | 如果有一个 `0` 坑,并且它旁边两个坑都是 `0`,那我们就可以种下一朵花。 43 | 44 | 需要能种至少 n 朵花,贪心策略很简单:就是遇到一个能种花的坑就把花种下,然后继续去找下一个坑。如果种完了 n 朵花就可以提前返回结果了。 45 | 46 | ### 复杂度分析 47 | 48 | - 时间复杂度:$O(N)$,N 为数组长度。 49 | - 空间复杂度:$O(1)$。 50 | 51 | ### 代码 52 | 53 | JavaScript Code 54 | 55 | ```js 56 | /** 57 | * @param {number[]} flowerbed 58 | * @param {number} n 59 | * @return {boolean} 60 | */ 61 | var canPlaceFlowers = function (flowerbed, n) { 62 | let i = 0; 63 | 64 | while (i < flowerbed.length) { 65 | // 如果找到一个 0,并且它前后都是 0,就可以种一朵花 66 | if (!flowerbed[i] && !flowerbed[i - 1] && !flowerbed[i + 1]) { 67 | n--; 68 | // 因为它旁边不能再种花了,所以 i+=2 跳过一个坑 69 | i += 2; 70 | } else { 71 | i++; 72 | } 73 | if (n <= 0) return true; 74 | } 75 | 76 | return false; 77 | }; 78 | ``` 79 | 80 | 更多题解可以访问:[https://github.com/suukii/91-days-algorithm](https://github.com/suukii/91-days-algorithm) 81 | -------------------------------------------------------------------------------- /topics/greedy/ext-candy.md: -------------------------------------------------------------------------------- 1 | # 135. 分发糖果 2 | 3 | https://leetcode-cn.com/problems/candy/ 4 | 5 | - [135. 分发糖果](#135-分发糖果) 6 | - [题目描述](#题目描述) 7 | - [方法 1:贪心](#方法-1贪心) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。 16 | 17 | 你需要按照以下要求,帮助老师给这些孩子分发糖果: 18 | 19 | 每个孩子至少分配到 1 个糖果。 20 | 相邻的孩子中,评分高的孩子必须获得更多的糖果。 21 | 那么这样下来,老师至少需要准备多少颗糖果呢? 22 | 23 | 示例 1: 24 | 25 | 输入: [1,0,2] 26 | 输出: 5 27 | 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。 28 | 示例 2: 29 | 30 | 输入: [1,2,2] 31 | 输出: 4 32 | 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。 33 | 第三个孩子只得到 1 颗糖果,这已满足上述两个条件。 34 | 35 | 来源:力扣(LeetCode) 36 | 链接:https://leetcode-cn.com/problems/candy 37 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 38 | ``` 39 | 40 | ## 方法 1:贪心 41 | 42 | ### 思路 43 | 44 | - 根据题意,对于每个孩子,我们只需要考虑他左右两侧的孩子。 45 | - 因为要准备 `尽量少` 的糖果,所以,我们只在当前孩子比左右孩子分数高的时候,才给他更多糖果。 46 | - 分两次遍历数组,第一次只考虑每个孩子左边的孩子,第二次只考虑每个孩子右边的孩子(左右顺序不重要,也可以先考虑右边再考虑左边)。 47 | 48 | ### 复杂度分析 49 | 50 | - 时间复杂度:$O(N)$。 51 | - 空间复杂度:$O(N)$。 52 | 53 | ### 代码 54 | 55 | JavaScript Code 56 | 57 | ```js 58 | /** 59 | * @param {number[]} ratings 60 | * @return {number} 61 | */ 62 | var candy = function (ratings) { 63 | if (!ratings || !ratings.length) return 0; 64 | 65 | // 每个孩子都有至少一颗糖果 66 | const candies = Array(ratings.length).fill(1); 67 | 68 | // 先考虑每个孩子左边的孩子,如果他比左边的分数高,就把他的糖果改成左边孩子糖果+1 69 | for (let i = 1; i < ratings.length; i++) { 70 | // 因为初始糖果数都是 1,所以 candies[i] <= candies[i - 1] 这个判断条件就没必要啦 71 | if (ratings[i] > ratings[i - 1]) candies[i] = candies[i - 1] + 1; 72 | } 73 | 74 | // 再考虑每个孩子右边的孩子,如果他比右边的分数高,而且他的糖果比右边的少, 75 | // 就将他的糖果数在右边孩子糖果的基础上加一 76 | for (let i = ratings.length - 2; i >= 0; i--) { 77 | if (ratings[i] > ratings[i + 1] && candies[i] <= candies[i + 1]) 78 | candies[i] = candies[i + 1] + 1; 79 | } 80 | 81 | // 求和 82 | return candies.reduce((a, b) => a + b, 0); 83 | }; 84 | ``` 85 | 86 | Python Code 87 | 88 | ```py 89 | class Solution(object): 90 | def candy(self, ratings): 91 | """ 92 | :type ratings: List[int] 93 | :rtype: int 94 | """ 95 | candies = [1] * len(ratings) 96 | 97 | # 考虑每个孩子的左边 98 | for i in range(1, len(ratings)): 99 | if ratings[i] > ratings[i - 1]: 100 | candies[i] = candies[i - 1] + 1 101 | 102 | # 考虑每个孩子的右边 103 | for i in range(len(ratings) - 2, -1, -1): 104 | if ratings[i] > ratings[i + 1] and candies[i] <= candies[i + 1]: 105 | candies[i] = candies[i + 1] + 1 106 | 107 | return sum(candies) 108 | ``` 109 | -------------------------------------------------------------------------------- /topics/greedy/ext-largest-perimeter-triangle.md: -------------------------------------------------------------------------------- 1 | # 976. 三角形的最大周长 2 | 3 | https://leetcode-cn.com/problems/largest-perimeter-triangle/ 4 | 5 | - [976. 三角形的最大周长](#976-三角形的最大周长) 6 | - [题目描述](#题目描述) 7 | - [方法 1:暴力(TLE)](#方法-1暴力tle) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | - [方法 2:贪心](#方法-2贪心) 12 | - [思路](#思路-1) 13 | - [复杂度分析](#复杂度分析-1) 14 | - [代码](#代码-1) 15 | 16 | ## 题目描述 17 | 18 | ``` 19 | 给定由一些正数(代表长度)组成的数组 A,返回由其中三个长度组成的、面积不为零的三角形的最大周长。 20 | 21 | 如果不能形成任何面积不为零的三角形,返回 0。 22 | 23 |   24 | 25 | 示例 1: 26 | 27 | 输入:[2,1,2] 28 | 输出:5 29 | 示例 2: 30 | 31 | 输入:[1,2,1] 32 | 输出:0 33 | 示例 3: 34 | 35 | 输入:[3,2,3,4] 36 | 输出:10 37 | 示例 4: 38 | 39 | 输入:[3,6,2,3] 40 | 输出:8 41 |   42 | 43 | 提示: 44 | 45 | 3 <= A.length <= 10000 46 | 1 <= A[i] <= 10^6 47 | 48 | 来源:力扣(LeetCode) 49 | 链接:https://leetcode-cn.com/problems/largest-perimeter-triangle 50 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 51 | ``` 52 | 53 | ## 方法 1:暴力(TLE) 54 | 55 | ### 思路 56 | 57 | 符合直觉的思路,三层循环,暴力枚举所以能组成三角形的三条边组合,找最大值,但复杂度过高超时了(64 / 84 个通过测试用例)。 58 | 59 | ### 复杂度分析 60 | 61 | - 时间复杂度:$O(N^3)$。 62 | - 空间复杂度:$O(1)$。 63 | 64 | ### 代码 65 | 66 | JavaScript Code 67 | 68 | ```js 69 | /** 70 | * @param {number[]} A 71 | * @return {number} 72 | */ 73 | var largestPerimeter = function (A) { 74 | let max = 0; 75 | for (let i = 0; i < A.length; i++) { 76 | for (let j = i + 1; j < A.length; j++) { 77 | for (let k = j + 1; k < A.length; k++) { 78 | if (isValidTri(A[i], A[j], A[k])) { 79 | max = Math.max(max, A[i] + A[j] + A[k]); 80 | } 81 | } 82 | } 83 | } 84 | return max; 85 | 86 | // **************************** 87 | function isValidTri(a, b, c) { 88 | return a + b > c && a + c > b && b + c > a; 89 | } 90 | }; 91 | ``` 92 | 93 | ## 方法 2:贪心 94 | 95 | ### 思路 96 | 97 | - 因为我们是要得到三角形的 `最大周长`,所以当然要尽可能地选 `最长` 的边。 98 | - 第一步我们先选定一条最长的边,那剩下的两条边怎么确定呢? 99 | - 首先,组成三角形的三条边需要满足条件:`(a + b > c) && (a + c > b) && (b + c > a)`,这个条件我们可以简化成 `a + b > c`,其中 c 是最长的边,a 和 b 是较短的边。 100 | - 当我们选定了最大的数字作为最长边 c 之后,只需要在剩下的数字中找出最大的两个: 101 | - 如果它们的和大于 c,那这个组合就是我们要找的答案了; 102 | - 如果它们的和小于 c,那也没有其他符合要求的数字了,这时我们需要放弃这个最长边,重新选择第二大的数字作为最长边 c。 103 | 104 | 具体做法就是降序排序,依次选择 `A[i]` 作为最长边进行判断。 105 | 106 | ### 复杂度分析 107 | 108 | - 时间复杂度:$O(NlogN)$。 109 | - 空间复杂度:$O(1)$。 110 | 111 | ### 代码 112 | 113 | JavaScript Code 114 | 115 | ```js 116 | /** 117 | * @param {number[]} A 118 | * @return {number} 119 | */ 120 | var largestPerimeter = function (A) { 121 | A.sort((a, b) => b - a); 122 | for (let i = 0; i < A.length - 2; i++) { 123 | if (A[i] < A[i + 1] + A[i + 2]) return A[i] + A[i + 1] + A[i + 2]; 124 | } 125 | return 0; 126 | }; 127 | ``` 128 | 129 | Python Code 130 | 131 | ```py 132 | class Solution(object): 133 | def largestPerimeter(self, A): 134 | """ 135 | :type A: List[int] 136 | :rtype: int 137 | """ 138 | A.sort(reverse=True) 139 | for i in range(len(A) - 2): 140 | if A[i] < A[i + 1] + A[i + 2]: 141 | return A[i] + A[i + 1] + A[i + 2] 142 | return 0 143 | ``` 144 | -------------------------------------------------------------------------------- /topics/greedy/ext-minimum-number-of-arrows-to-burst-balloons.md: -------------------------------------------------------------------------------- 1 | # 452. 用最少数量的箭引爆气球 2 | 3 | https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/ 4 | 5 | ## 题目描述 6 | 7 | ``` 8 | 在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。 9 | 10 | 一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足  xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。 11 | 12 | 给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。 13 | 14 |   15 | 示例 1: 16 | 17 | 输入:points = [[10,16],[2,8],[1,6],[7,12]] 18 | 输出:2 19 | 解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球 20 | 示例 2: 21 | 22 | 输入:points = [[1,2],[3,4],[5,6],[7,8]] 23 | 输出:4 24 | 示例 3: 25 | 26 | 输入:points = [[1,2],[2,3],[3,4],[4,5]] 27 | 输出:2 28 | 示例 4: 29 | 30 | 输入:points = [[1,2]] 31 | 输出:1 32 | 示例 5: 33 | 34 | 输入:points = [[2,3],[2,3]] 35 | 输出:1 36 |   37 | 38 | 提示: 39 | 40 | 0 <= points.length <= 104 41 | points[i].length == 2 42 | -231 <= xstart < xend <= 231 - 1 43 | 44 | 来源:力扣(LeetCode) 45 | 链接:https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons 46 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 47 | ``` 48 | 49 | ## 方法 1: 贪心 50 | 51 | ### 思路 52 | 53 | ### 复杂度分析 54 | 55 | - 时间复杂度:$O(NlogN)$,N 为数组长度,排序的时间。 56 | - 空间复杂度:$O(1)$。 57 | 58 | ### 代码 59 | 60 | JavaScript Code 61 | 62 | ```js 63 | 64 | ``` 65 | 66 | 更多题解可以访问:[https://github.com/suukii/91-days-algorithm](https://github.com/suukii/91-days-algorithm) 67 | -------------------------------------------------------------------------------- /topics/greedy/ext-non-overlapping-intervals.md: -------------------------------------------------------------------------------- 1 | # 435. 无重叠区间 2 | 3 | https://leetcode-cn.com/problems/non-overlapping-intervals/ 4 | 5 | - [435. 无重叠区间](#435-无重叠区间) 6 | - [题目描述](#题目描述) 7 | - [方法 1: 贪心](#方法-1-贪心) 8 | - [思路](#思路) 9 | - [复杂度分析](#复杂度分析) 10 | - [代码](#代码) 11 | 12 | ## 题目描述 13 | 14 | ``` 15 | 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 16 | 17 | 注意: 18 | 19 | 可以认为区间的终点总是大于它的起点。 20 | 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。 21 | 示例 1: 22 | 23 | 输入: [ [1,2], [2,3], [3,4], [1,3] ] 24 | 25 | 输出: 1 26 | 27 | 解释: 移除 [1,3] 后,剩下的区间没有重叠。 28 | 示例 2: 29 | 30 | 输入: [ [1,2], [1,2], [1,2] ] 31 | 32 | 输出: 2 33 | 34 | 解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。 35 | 示例 3: 36 | 37 | 输入: [ [1,2], [2,3] ] 38 | 39 | 输出: 0 40 | 41 | 解释: 你不需要移除任何区间,因为它们已经是无重叠的了。 42 | 43 | 来源:力扣(LeetCode) 44 | 链接:https://leetcode-cn.com/problems/non-overlapping-intervals 45 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 46 | ``` 47 | 48 | ## 方法 1: 贪心 49 | 50 | ### 思路 51 | 52 | 因为需要**移除尽量少**的区间,换句话说,就是要**保留尽量多**的区间。在选择要保留的区间的时候,需要考虑的是区间的**结束时间**,因为结束时间越早,留给其他区间的空间就越多,就越有可能保留更多的区间。所以我们采取的贪心策略是:**优先保留结束时间小并且不与其他区间相交的区间**。 53 | 54 | 具体实现就是要按区间结束时间升序排序。 55 | 56 | > 注意:需要根据实际情况判断按区间开头还是按区间结尾排序。 57 | 58 | 60 | 61 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/435_0.png) 62 | 63 | ### 复杂度分析 64 | 65 | - 时间复杂度:$O(NlogN)$, N 为数组长度,排序的时间。 66 | - 空间复杂度:$O(1)$。 67 | 68 | ### 代码 69 | 70 | JavaScript Code 71 | 72 | ```js 73 | /** 74 | * @param {number[][]} intervals 75 | * @return {number} 76 | */ 77 | var eraseOverlapIntervals = function (intervals) { 78 | // 按区间结尾升序排序 79 | intervals.sort((a, b) => a[1] - b[1]); 80 | 81 | let ans = 0; 82 | let cur = 0, 83 | next = 1; 84 | 85 | while (next < intervals.length) { 86 | // 如果两个区间相交,则移除 next 区间 (next++,cur不动) 87 | if (intervals[cur][1] > intervals[next][0]) { 88 | ans++; 89 | next++; 90 | } 91 | // 如果两个区间不相交,则继续比较后面两个区间 (cur=next, next++) 92 | else { 93 | cur = next; 94 | next++; 95 | } 96 | } 97 | 98 | return ans; 99 | }; 100 | ``` 101 | 102 | 更多题解可以访问:[https://github.com/suukii/91-days-algorithm](https://github.com/suukii/91-days-algorithm) 103 | -------------------------------------------------------------------------------- /topics/sliding-window/ext-shortest-unsorted-continuous-subarray.md: -------------------------------------------------------------------------------- 1 | # 581.最短无序连续子数组 2 | 3 | https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray 4 | 5 | - [581.最短无序连续子数组](#581最短无序连续子数组) 6 | - [题目描述](#题目描述) 7 | - [方法 1:滑动窗口](#方法-1滑动窗口) 8 | - [思路](#思路) 9 | - [代码](#代码) 10 | 11 | ## 题目描述 12 | 13 | ``` 14 | 给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。 15 | 16 | 你找到的子数组应是最短的,请输出它的长度。 17 | 18 | 示例 1: 19 | 20 | 输入: [2, 6, 4, 8, 10, 9, 15] 21 | 输出: 5 22 | 解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。 23 | 说明 : 24 | 25 | 输入的数组长度范围在 [1, 10,000]。 26 | 输入的数组可能包含重复元素 ,所以升序的意思是<=。 27 | 28 | 来源:力扣(LeetCode) 29 | 链接:https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray 30 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 31 | ``` 32 | 33 | ## 方法 1:滑动窗口 34 | 35 | ### 思路 36 | 37 | - 先找出数组两端已经排好序的两个子数组,剩下的中间那段就是**可能的** `最短无序连续子数组`; 38 | - 但由于这个 `无序子数组` 中可能会出现需要插入 `有序子数组` 中的元素,比如图中的数字 4, 39 | - 我们可以把 `无序子数组` 看作一个滑动窗口,当出现了上面出现的这种元素时,就扩大窗口的范围; 40 | 41 | ![](https://cdn.jsdelivr.net/gh/suukii/91-days-algorithm/assets/581_0.png) 42 | 43 | ### 代码 44 | 45 | JavaScript Code 46 | 47 | ```js 48 | /** 49 | * @param {number[]} nums 50 | * @return {number} 51 | */ 52 | var findUnsortedSubarray = function (nums) { 53 | if (nums.length === 0) return 0; 54 | 55 | let l = 0, 56 | r = nums.length - 1; 57 | while (nums[l + 1] >= nums[l]) l++; 58 | while (nums[r - 1] <= nums[r]) r--; 59 | 60 | if (r <= l) return 0; 61 | 62 | const unsorted = nums.slice(l, r + 1), 63 | min = Math.min(...unsorted), 64 | max = Math.max(...unsorted); 65 | 66 | while (nums[l - 1] > min) l--; 67 | while (nums[r + 1] < max) r++; 68 | return r - l + 1; 69 | }; 70 | ``` 71 | 72 | JavaScript Code 73 | 74 | ```js 75 | /** 76 | * @param {number[]} nums 77 | * @return {number} 78 | */ 79 | var findUnsortedSubarray = function (nums) { 80 | if (nums.length === 0) return 0; 81 | 82 | let l = 0, 83 | r = nums.length - 1; 84 | while (nums[l + 1] >= nums[l]) l++; 85 | while (nums[r - 1] <= nums[r]) r--; 86 | 87 | if (r <= l) return 0; 88 | 89 | let p = l, 90 | end = r; 91 | while (p <= end) { 92 | while (nums[p] < nums[l]) l--; 93 | while (nums[p] > nums[r]) r++; 94 | p++; 95 | } 96 | return r - (l + 1); 97 | }; 98 | ``` 99 | 100 | 更多题解可以访问:[https://github.com/suukii/91-days-algorithm](https://github.com/suukii/91-days-algorithm) 101 | --------------------------------------------------------------------------------