commit 872a3c2d62b96ac1373bfb34a918e812b24d6dcb Author: leonard Date: Fri May 22 18:17:19 2020 +0800 Initial commit diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..bbf7425 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +patreon: fe_lucifer diff --git a/.github/ISSUE_TEMPLATE/daily-problem.md b/.github/ISSUE_TEMPLATE/daily-problem.md new file mode 100644 index 0000000..f5155c2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/daily-problem.md @@ -0,0 +1,12 @@ +--- +name: Daily Problem +about: Contribute Daily Problem +title: "【每日一题】- 2020-xx-xx - xxx " +labels: Daily Question +assignees: '' + +--- + +[anything] + +题目地址:xxxxxx diff --git a/.github/ISSUE_TEMPLATE/translation.md b/.github/ISSUE_TEMPLATE/translation.md new file mode 100644 index 0000000..effb306 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/translation.md @@ -0,0 +1,10 @@ +--- +name: Translation +about: translation +title: 'feat(translation): xxxxxxx' +labels: 国际化 +assignees: '' + +--- + + diff --git a/.github/calibre/image-actions.yml b/.github/calibre/image-actions.yml new file mode 100644 index 0000000..8c06d99 --- /dev/null +++ b/.github/calibre/image-actions.yml @@ -0,0 +1,8 @@ +jpeg: + quality: 80 +png: + quality: 80 +webp: + quality: 80 +ignorePaths: + - "node_modules/**" \ No newline at end of file diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..c9066e6 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - help wanted +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false \ No newline at end of file diff --git a/.github/workflows/calibreapp-image-actions.yml b/.github/workflows/calibreapp-image-actions.yml new file mode 100644 index 0000000..9cb2ce1 --- /dev/null +++ b/.github/workflows/calibreapp-image-actions.yml @@ -0,0 +1,17 @@ +name: Compress images +on: pull_request +jobs: + build: + name: calibreapp/image-actions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: calibreapp/image-actions + uses: docker://calibreapp/github-image-actions + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + wip: + name: "Set status" + runs-on: ubuntu-latest + steps: + - uses: wip/action@master \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dde2f7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.idea/* \ No newline at end of file diff --git a/CONTRIBUTING.en.md b/CONTRIBUTING.en.md new file mode 100644 index 0000000..244950a --- /dev/null +++ b/CONTRIBUTING.en.md @@ -0,0 +1,23 @@ +# Contributing + +## Translation + +Please pick any work without translation by `submit new issue`. English version and Chinese version are distinguished by file name, e.g. Chinese version file name abc.md, the corresponding English version should be abc.en.md. + +Manual translation instead of machine translation, there is no need to translate the technical jargon. + +## Contributing to problems + +Please follow the template of "problems", what you need to submit are: + +- Problem and solution markdown file +- Add the link of the solution in README.md +- Add the link of the solution in README.en.md (optional) +- draw.io file(xml) or pictures (optional) + +> Template for reference: [1014.best-sightseeing-pair](./templates/problems/1014.best-sightseeing-pair.md) +> Online painting tools like https://excalidraw.com/, draw.io, processon or iPad apps + +## Contributing to daily problem + +- Please follow the template of "daily problem" and submit the issue with correct tags (can refer to official tags on LeetCode). \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bde3efd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# 贡献我们 + +## 翻译 + +只需要看哪个没有被翻译即可认领,认领方式为`提交新的issue`形式。英文版本和中文版通过文件名区分。比如中文的文件名是 abc.md,那么英文的应该是 abc.en.md。 + +尽可能使用意译,避免机械性的英文翻译,专有名词无需翻译。 + +## 增加题解 + +只需要按照其他题目格式提交即可,需要注意的是,你需要提交以下文件: + +- 题解文件 +- README.md 增加您新的题解 +- README.en.md (如果你是英文题解的话) +- drawio 文件 或者图片(如果你画图的话) + +> 模板可参考 [1014.best-sightseeing-pair](./templates/problems/1014.best-sightseeing-pair.md) +> 也可以使用其他画图工具,比如 https://excalidraw.com/ 或者 draw.io 或者 processon 或者 ipad 画图 + +## 贡献每日一题 + +- 直接按照“每日一题”格式要求提交 issue 即可,需要注意的是,要打上正确的 tag,如果 LeetCode 题目,可以参考官方给的 tag。 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..de40f2d --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015-2016 Netflix, Inc., Microsoft Corp. and contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..3923c23 --- /dev/null +++ b/README.en.md @@ -0,0 +1,324 @@ +# LeetCode + +[![Travis](https://img.shields.io/badge/language-C++-green.svg)]() +[![Travis](https://img.shields.io/badge/language-JavaScript-yellow.svg)]() +[![Travis](https://img.shields.io/badge/language-Python-red.svg)]() +[![Travis](https://img.shields.io/badge/language-Java-blue.svg)]() +![Total visitor](https://visitor-count-badge.herokuapp.com/total.svg?repo_id=azl397985856.leetcode.en) +![Visitors in today](https://visitor-count-badge.herokuapp.com/today.svg?repo_id=azl397985856.leetcode.en) + +> since 2019-09-03 19:40 + +[简体中文](./README.md) | English + +--- + +![leetcode.jpeg](./assets/leetcode.jpeg) + +This essay records the course of and my emotion to this project from initialization to 10,000 stars. +[Milestone for 10,000+ stars](./thanksGiving.md) + +If you are interested in this project, **do not mean your star**. This project will be **supported for a long enough time** by the community. Thanks for every audience and contributor. + +## Introduction + +![leetcode.jpeg](./assets/leetcode.jpeg) + +LeetCode Solutions: A Journey of Problem Solving. + +This repository is divided into five parts for now: + +- The first part is the solutions to some classic problems on LeetCode, including the idea thinkings, key points and code implementations. + +- The second part is a summary of data structures and algorithms. + +- The third part is [Anki flashcards](https://apps.ankiweb.net) that organizes the LeetCode problems in a certain way to make it easier to remember. + +- The fourth part is daily challenges which were held at group chat. We usually solve one problem altogether to get more feedback. Moreover, the problems can be drafted to add to the problem solving module. + +- The fifth part is a future planning on content that will be introduced into the above parts. + +> Only when having mastered the basic data structures and algorithms can you solve complex problems easily. + +## About me + +I, a programmer, am all passionate about technology. + +Used to write `.net` and `Java`, I am a frontend engineer and focused on the engineering, optimization and standardization for frontend. + +If you want to do some contributions or collaborations, just feel free to contact me via [azl397985856@gmail.com]. + +## Usage Instructions + +- For the parts that were added recently, there will be a 🆕 behind. +- For the parts that were updated recently, there will be a 🖊 behind. +- For the parts that have been translated, there will be a ✅ behind. +- Here will be the place to update Anki Flashcards in the future as well. +- Here is a mind mapping graph showing the summary of categorizations of problems that are questioned frequently in interviews. We could analyze according to the information in the graph. + +![leetcode-zhihu](./assets//leetcode-zhihu.jpg) + +(Picture credited by [LeetCode-cn](https://www.zhihu.com/question/24964987/answer/586425979).) + +The algorithms mainly include: + +- Basic skills: Divide-and-Conquer; Binary; Greedy +- Sorting algorithms: Quicksort; Merge Sort; Counting Sort +- Searching algorithms: Backtracking; Recursion; Depth-First-Search (DFS); Breath-First-Search (BFS); Binary Search Tree; etc. +- Graph theory: Shortest Path Problem; Minimal Spanning Tree +- Dynamic Programming: Knapsack Problem; Longest Common Subsequence (LCS) Problem + +The data structures mainly include: + +- Array and linked list: Singly/Doubly-Linked List +- Stack and queue +- Hash table +- Heap: Min-Max Heap +- Tree and Graph: Lowest Common Ancestor (LCA); Disjoint-Set +- String: Prefix Tree (Trie); Suffix Tree + +## Previews (Translation in Progress) + +[0042.trapping-rain-water](./problems/42.trapping-rain-water.md): + +![0042.trapping-rain-water](./assets/problems/42.trapping-rain-water-1.png) + +[0547.friend-circles](./problems/547.friend-circles-en.md) ✅: + +![friend circle BFS](./assets/problems/547.friend-circle-bfs.png) + +[backtrack problems](./problems/90.subsets-ii-en.md): + +![backtrack](./assets/problems/backtrack.png) + +[0198.house-robber](./problems/198.house-robber.md): + +![198.house-robber](./assets/problems/198.house-robber.png) + +[0454.4-sum-ii](./problems/454.4-sum-ii.md): + +![454.4-sum-ii](./assets/problems/454.4-sum-ii.png) + +## Top Problems Progress + +- [Top 100 Liked Questions](https://leetcode.com/problemset/top-100-liked-questions/) (84 / 100) + +- [Top Interview Questions](https://leetcode.com/problemset/top-interview-questions/) (115 / 145) + +## Portals + +### Solutions to LeetCode Classic Problems + +> Here only lists some **representative problems** but not all. + +#### Easy (Translation in Progress) +- [0001.TwoSum](./problems/1.TwoSum.en.md)🆕✅ +- [0020.Valid Parentheses](./problems/20.validParentheses.md) +- [0021.MergeTwoSortedLists](./problems/21.MergeTwoSortedLists.md) 🆕 +- [0026.remove-duplicates-from-sorted-array](./problems/26.remove-duplicates-from-sorted-array.md) +- [0053.maximum-sum-subarray](./problems/53.maximum-sum-subarray-en.md) 🆕✅ +- [0088.merge-sorted-array](./problems/88.merge-sorted-array.md) +- [0104.maximum-depth-of-binary-tree](./problems/104.maximum-depth-of-binary-tree.md) +- [0121.best-time-to-buy-and-sell-stock](./problems/121.best-time-to-buy-and-sell-stock.md) +- [0122.best-time-to-buy-and-sell-stock-ii](./problems/122.best-time-to-buy-and-sell-stock-ii.md) +- [0125.valid-palindrome](./problems/125.valid-palindrome.md) 🆕 +- [0136.single-number](./problems/136.single-number.md) +- [0155.min-stack](./problems/155.min-stack.md) 🆕 +- [0167.two-sum-ii-input-array-is-sorted](./problems/167.two-sum-ii-input-array-is-sorted.md) +- [0172.factorial-trailing-zeroes](./problems/172.factorial-trailing-zeroes.md) +- [0169.majority-element](./problems/169.majority-element.md) +- [0190.reverse-bits](./problems/190.reverse-bits.md) +- [0191.number-of-1-bits](./problems/191.number-of-1-bits.md) +- [0198.house-robber](./problems/198.house-robber.md) +- [0203.remove-linked-list-elements](./problems/203.remove-linked-list-elements.md) +- [0206.reverse-linked-list](./problems/206.reverse-linked-list.md) +- [0219.contains-duplicate-ii](./problems/219.contains-duplicate-ii.md) +- [0226.invert-binary-tree](./problems/226.invert-binary-tree.md) +- [0232.implement-queue-using-stacks](./problems/232.implement-queue-using-stacks.md) 🆕 +- [0263.ugly-number](./problems/263.ugly-number.md) +- [0283.move-zeroes](./problems/283.move-zeroes.md) +- [0342.power-of-four](./problems/342.power-of-four.md) +- [0371.sum-of-two-integers](./problems/371.sum-of-two-integers.md) +- [0349.intersection-of-two-arrays](./problems/349.intersection-of-two-arrays.md) +- [0437.path-sum-iii](./problems/437.path-sum-iii.md) 🆕 +- [0455.AssignCookies](./problems/455.AssignCookies.md) 🆕 +- [0501.find-mode-in-binary-search-tree](./problems/501.Find-Mode-in-Binary-Search-Tree.md) 🆕 +- [0575.distribute-candies](./problems/575.distribute-candies.md) + +#### Medium (Translation in Progress) + +- [0002. Add Two Numbers](./problems/2.addTwoNumbers.md) +- [0003. Longest Substring Without Repeating Characters](./problems/3.longestSubstringWithoutRepeatingCharacters.md) +- [0005.longest-palindromic-substring](./problems/5.longest-palindromic-substring.md) +- [0011.container-with-most-water](./problems/11.container-with-most-water.md) +- [0015.3-sum](./problems/15.3-sum.md) +- [0017.Letter-Combinations-of-a-Phone-Number](./problems/17.Letter-Combinations-of-a-Phone-Number.md) 🆕 +- [0019. Remove Nth Node From End of List](./problems/19.removeNthNodeFromEndofList.md) +- [0022.GenerateParentheses](./problems/22.GenerateParentheses.md) 🆕 +- [0024. Swap Nodes In Pairs](./problems/24.swapNodesInPairs.md) +- [0029.divide-two-integers](./problems/29.divide-two-integers.md) +- [0031.next-permutation](./problems/31.next-permutation.md) +- [0033.search-in-rotated-sorted-array](./problems/33.search-in-rotated-sorted-array.md) +- [0039.combination-sum](./problems/39.combination-sum.md) +- [0040.combination-sum-ii](./problems/40.combination-sum-ii.md) +- [0046.permutations](./problems/46.permutations.md) +- [0047.permutations-ii](./problems/47.permutations-ii.md) +- [0048.rotate-image](./problems/48.rotate-image.md) +- [0049.group-anagrams](./problems/49.group-anagrams.md) +- [0055.jump-game](./problems/55.jump-game.md) +- [0056.merge-intervals](./problems/56.merge-intervals.md) +- [0062.unique-paths](./problems/62.unique-paths.md) +- [0073.set-matrix-zeroes](./problems/73.set-matrix-zeroes.md) +- [0075.sort-colors](./problems/75.sort-colors.md) +- [0078.subsets](./problems/78.subsets-en.md)✅ +- [0079.word-search](./problems/79.word-search-en.md) ✅ +- [0086.partition-list](./problems/86.partition-list.md) +- [0090.subsets-ii](./problems/90.subsets-ii-en.md)✅ +- [0091.decode-ways](./problems/91.decode-ways.md) +- [0092.reverse-linked-list-ii](./problems/92.reverse-linked-list-ii.md) +- [0094.binary-tree-inorder-traversal](./problems/94.binary-tree-inorder-traversal.md) +- [0098.validate-binary-search-tree](./problems/98.validate-binary-search-tree.md) +- [0102.binary-tree-level-order-traversal](./problems/102.binary-tree-level-order-traversal.md) +- [0103.binary-tree-zigzag-level-order-traversal](./problems/103.binary-tree-zigzag-level-order-traversal.md) +- [105.Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.md](./problems/105.Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.md) +- [0113.path-sum-ii](./problems/113.path-sum-ii.md) +- [0129.sum-root-to-leaf-numbers](./problems/129.sum-root-to-leaf-numbers.md) +- [0130.surrounded-regions](./problems/130.surrounded-regions.md) +- [0131.palindrome-partitioning](./problems/131.palindrome-partitioning.md) +- [0139.word-break](./problems/139.word-break.md) +- [0144.binary-tree-preorder-traversal](./problems/144.binary-tree-preorder-traversal.md) +- [0150.evaluate-reverse-polish-notation](./problems/150.evaluate-reverse-polish-notation.md) +- [0152.maximum-product-subarray](./problems/152.maximum-product-subarray.md) +- [0199.binary-tree-right-side-view](./problems/199.binary-tree-right-side-view.md) +- [0200.number-of-islands](./problems/200.number-of-islands.md) 🆕 +- [0201.bitwise-and-of-numbers-range](./problems/201.bitwise-and-of-numbers-range.md) +- [0208.implement-trie-prefix-tree](./problems/208.implement-trie-prefix-tree.md) +- [0209.minimum-size-subarray-sum](./problems/209.minimum-size-subarray-sum.md) +- [0215.kth-largest-element-in-an-array](./problems/215.kth-largest-element-in-an-array.md) 🆕 +- [0221.maximal-square](./problems/221.maximal-square.md) +- [0229.majority-element-ii](./problems/229.majority-element-ii.md) 🆕 +- [0230.kth-smallest-element-in-a-bst](./problems/230.kth-smallest-element-in-a-bst.md) +- [0236.lowest-common-ancestor-of-a-binary-tree](./problems/236.lowest-common-ancestor-of-a-binary-tree.md) +- [0238.product-of-array-except-self](./problems/238.product-of-array-except-self.md) +- [0240.search-a-2-d-matrix-ii](./problems/240.search-a-2-d-matrix-ii.md) +- [0279.perfect-squares](./problems/279.perfect-squares.md) +- [0309.best-time-to-buy-and-sell-stock-with-cooldown](./problems/309.best-time-to-buy-and-sell-stock-with-cooldown.md) +- [0322.coin-change](./problems/322.coin-change.md) +- [0328.odd-even-linked-list](./problems/328.odd-even-linked-list.md) +- [0334.increasing-triplet-subsequence](./problems/334.increasing-triplet-subsequence.md) +- [0365.water-and-jug-problem](./problems/365.water-and-jug-problem.md) +- [0378.kth-smallest-element-in-a-sorted-matrix](./problems/378.kth-smallest-element-in-a-sorted-matrix.md) +- [0416.partition-equal-subset-sum](./problems/416.partition-equal-subset-sum.md) +- [0445.add-two-numbers-ii](./problems/445.add-two-numbers-ii.md) +- [0454.4-sum-ii](./problems/454.4-sum-ii.md) +- [0474.ones-and-zeros](./problems/474.ones-and-zeros-en.md)✅ +- [0494.target-sum](./problems/494.target-sum.md) +- [0516.longest-palindromic-subsequence](./problems/516.longest-palindromic-subsequence.md) +- [0518.coin-change-2](./problems/518.coin-change-2.md) +- [0547.friend-circles](./problems/547.friend-circles-en.md) 🆕✅ +- [0560.subarray-sum-equals-k](./problems/560.subarray-sum-equals-k.en.md) ✅ +- [0609.find-duplicate-file-in-system](./problems/609.find-duplicate-file-in-system.md) +- [0875.koko-eating-bananas](./problems/875.koko-eating-bananas.md) +- [0877.stone-game](./problems/877.stone-game.md) +- [0887.super-egg-drop](./problems/887.super-egg-drop.md) +- [0900.rle-iterator](./problems/900.rle-iterator.md) +- [0912.sort-an-array](./problems/912.sort-an-array.md) 🆕 +- [1011.capacity-to-ship-packages-within-d-days](./problems/1011.capacity-to-ship-packages-within-d-days-en.md) 🆕✅ +- [1031.maximum-sum-of-two-non-overlapping-subarrays](./problems/1031.maximum-sum-of-two-non-overlapping-subarrays.md) +- [1218.longest-arithmetic-subsequence-of-given-difference.md](./problems/1218.longest-arithmetic-subsequence-of-given-difference.md) 🆕 +- [1371.find-the-longest-substring-containing-vowels-in-even-counts](./problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.en.md) 🆕✅ + + +#### Hard (Translation in Progress) + +- [0004.median-of-two-sorted-array](./problems/4.median-of-two-sorted-array.md) 🆕 +- [0023.merge-k-sorted-lists](./problems/23.merge-k-sorted-lists.md) +- [0025.reverse-nodes-in-k-group](./problems/25.reverse-nodes-in-k-groups-en.md) 🆕✅ +- [0032.longest-valid-parentheses](./problems/32.longest-valid-parentheses.md) 🆕 +- [0042.trapping-rain-water](./problems/42.trapping-rain-water.md) +- [0052.N-Queens-II](./problems/52.N-Queens-II.md) 🆕 +- [0124.binary-tree-maximum-path-sum](./problems/124.binary-tree-maximum-path-sum.md) +- [0128.longest-consecutive-sequence](./problems/128.longest-consecutive-sequence.md) +- [0145.binary-tree-postorder-traversal](./problems/145.binary-tree-postorder-traversal.md) +- [0239.sliding-window-maximum](./problems/239.sliding-window-maximum.md) +- [0295.find-median-from-data-stream](./problems/295.find-median-from-data-stream.md) 🆕 +- [0301.remove-invalid-parentheses](./problems/301.remove-invalid-parentheses.md) +- [0460.lfu-cache](./problems/460.lfu-cache.md) 🆕 +- [1168.optimize-water-distribution-in-a-village](./problems/1168.optimize-water-distribution-in-a-village-en.md) 🆕✅ + +### Summary of Data Structures and Algorithm + +- [Data Structure](./thinkings/basic-data-structure-en.md)✅ +- [Basic Algorithm](./thinkings/basic-algorithm-en.md)✅ +- [Binary Tree Traversal](./thinkings/binary-tree-traversal.en.md)✅ +- [Dynamic Programming](./thinkings/dynamic-programming-en.md)✅ +- [Huffman Encode and Run Length Encode](./thinkings/run-length-encode-and-huffman-encode-en.md)✅ +- [Bloom Filter](./thinkings/bloom-filter-en.md)✅ +- [String Problems](./thinkings/string-problems-en.md)✅ +- [Sliding Window Technique](./thinkings/slide-window.en.md)✅ + +### Anki Flashcards + +Anki falshcards would be mainly two parts: the mappings from key points to problems; the mappings from problems to idea thinks, key points and code implementations. + +All flashcards are put in [anki-card](./assets/anki/leetcode.apkg). + +> Please check [here](https://apps.ankiweb.net/) for more about the usage of Anki. + +Latest updated flashcards (only lists the front page): + +- What is the key point of the binary search algorithm? Related problems? +- How to simplify the operations using the features of stacks? Related problems? +- The thinkings and related problems of double-pointers problems? +- The thinkings and related problems of sliding window problems? +- The thinkings and related problems of backtracking? +- The thinkings and related problems of number theory? +- The thinkings and related problems of bit operations? + +> WIP: the translation of the flashcards are on the way. + +> problems added:#2 #3 #11 + +### Daily Problems + +- [summary](./daily/) + +- [project](https://github.com/azl397985856/leetcode/projects/1) + +### Future Plans + +- [Complete Anki Flashcards](./assets/anki/) + +- [Collection of String Problem](./todo/str/) + +## Community Chat Groups + +We're still on the early stage, so feedback from community is very welcome. For sake of reducing the costs of communication, I created some chat groups. + +### Telegram + +[http://t.me/leetcode_intl](http://t.me/leetcode_intl) + +### QQ (For China Region) + +![qq-group-chat](./assets/qq-group-chat.png) + +### WeChat (For China Region) + +![wechat-group-chat](./assets/wechat-group-chat.jpeg) + +(Add this bot and reply "leetcode" to join the group.) + +## Contribution + +- If you have any ideas, [Issues](https://github.com/azl397985856/leetcode/issues) or chat in groups. +- If you want to commit to the repository, Pull Request is welcome. Here is the [CONTRIBUTION GUIDE](./CONTRIBUTING.en.md) +- If you want to edit images resources in this project, [here](./assets/drawio/) lists the files that can be edited on [draw.io](https://www.draw.io/). + +## Thank you + +A big Thank you to every [contributor](https://github.com/azl397985856/leetcode/graphs/contributors) of this project. + +## License + +[Apache-2.0](./LICENSE.txt) diff --git a/README.md b/README.md new file mode 100644 index 0000000..ccf5fdd --- /dev/null +++ b/README.md @@ -0,0 +1,405 @@ +# LeetCode + +[![Travis](https://img.shields.io/badge/language-C++-green.svg)]() +[![Travis](https://img.shields.io/badge/language-JavaScript-yellow.svg)]() +[![Travis](https://img.shields.io/badge/language-Python-red.svg)]() +[![Travis](https://img.shields.io/badge/language-Java-blue.svg)]() + +[![](https://img.shields.io/badge/WeChat-微信群-brightgreen)](#关注我) +[![](https://img.shields.io/badge/公众号-脑洞前端-blueviolet)](#关注我) +[![](https://img.shields.io/badge/Juejin-掘金-blue)](https://juejin.im/user/58af98305c497d0067780b3b) +[![](https://img.shields.io/badge/Zhihu-知乎-blue)](https://www.zhihu.com/people/lu-xiao-13-70) +[![](https://img.shields.io/badge/bilili-哔哩哔哩-ff69b4)](https://space.bilibili.com/519510412/) + +![历史共访问次数](https://visitor-count-badge.herokuapp.com/total.svg?repo_id=azl397985856.leetcode) +![今天被访问次数](https://visitor-count-badge.herokuapp.com/today.svg?repo_id=azl397985856.leetcode) + +> 统计数据的时间是从 2019-09-03 19:40 起至今 + +简体中文 | [English](./README.en.md) + +--- + +![leetcode.jpeg](./assets/leetcode.jpeg) + +- 2019-07-10 :[纪念项目 Star 突破 1W 的一个短文](./thanksGiving.md), 记录了项目的"兴起"之路,大家有兴趣可以看一下,如果对这个项目感兴趣,请**点击一下 Star**, 项目会**持续更新**,感谢大家的支持。 + +- 2019-10-08: [纪念 LeetCode 项目 Star 突破 2W](./thanksGiving2.md),并且 Github 搜索“LeetCode”,排名第一。 + +- 2020-04-12: [项目突破三万 Star](./thanksGiving3.md)。 +- 2020-04-14: 官网`力扣加加`上线啦 💐💐💐💐💐,有专题讲解,每日一题,下载区和视频题解,后续会增加更多内容,还不赶紧收藏起来?地址:http://leetcode-solution.cn/ + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvenxvjlsj30z90dtdhw.jpg) + +## 介绍 + +leetcode 题解,记录自己的 leetcode 解题之路。 + +本仓库目前分为**五个**部分: + +- 第一个部分是 leetcode 经典题目的解析,包括思路,关键点和具体的代码实现。 + +- 第二部分是对于数据结构与算法的总结 + +- 第三部分是 anki 卡片, 将 leetcode 题目按照一定的方式记录在 anki 中,方便大家记忆。 + +- 第四部分是每日一题,每日一题是在交流群(包括微信和 qq)里进行的一种活动,大家一起 解一道题,这样讨论问题更加集中,会得到更多的反馈。而且 这些题目可以被记录下来,日后会进行筛选添加到仓库的题解模块。 + +- 第五部分是计划, 这里会记录将来要加入到以上三个部分内容 + +> 只有熟练掌握基础的数据结构与算法,才能对复杂问题迎刃有余。 + +## 关于我 + +擅长前端工程化,前端性能优化,前端标准化等,做过。net, 搞过 Java,现在是一名前端工程师,我的个人博客:https://lucifer.ren/blog/ + +我经常会在开源社区进行一些输出和分享,比较受欢迎的有 [宇宙最强的前端面试指南](https://github.com/azl397985856/fe-interview) +和 [我的第一本小书](https://github.com/azl397985856/automate-everything)。目前本人正在写一本关于《leetcode 题解》的实体书,感兴趣的可以通过邮箱或者微信联系我,我会在出版的第一时间通知你,并给出首发优惠价。有需要可以直接群里联系我,或者发送到我的个人邮箱 [azl397985856@gmail.com]。 新书详情戳这里:[《或许是一本可以彻底改变你刷 LeetCode 效率的题解书》](https://lucifer.ren/blog/2020/04/07/leetcode-book.intro/) + +## 食用指南 + +- 对于最近添加的部分, 后面会有 🆕 标注 +- 对于最近更新的部分, 后面会有 🖊 标注 +- 将来会在这里更新 anki 卡片 +- 这里有一份 leetcode 官方账号在知乎上给出的一个《互联网公司最常见的面试算法题有哪些?》的答案,我这里尽量去覆盖回答中的题目和知识点 + 原文地址: https://www.zhihu.com/question/24964987/answer/586425979 + +- 这里有一份我在知乎上的回答 [《大家都是如何刷 LeetCode 的?》](https://www.zhihu.com/question/280279208/answer/824585814) + +- 这里有一张互联网公司面试中经常考察的问题类型总结的思维导图,我们可以结合图片中的信息分析一下。 + +![leetcode-zhihu](./assets//leetcode-zhihu.jpg) + +(图片来自 leetcode) + +其中算法,主要是以下几种: + +- 基础技巧:分治、二分、贪心 +- 排序算法:快速排序、归并排序、计数排序 +- 搜索算法:回溯、递归、深度优先遍历,广度优先遍历,二叉搜索树等 +- 图论:最短路径、最小生成树 +- 动态规划:背包问题、最长子序列 + +数据结构,主要有如下几种: + +- 数组与链表:单 / 双向链表 +- 栈与队列 +- 哈希表 +- 堆:最大堆 / 最小堆 +- 树与图:最近公共祖先、并查集 +- 字符串:前缀树(字典树) / 后缀树 + +## 精彩预告 + +[0042.trapping-rain-water](./problems/42.trapping-rain-water.md): + +![0042.trapping-rain-water](./assets/problems/42.trapping-rain-water-1.png) + +[0547.friend-circles](./problems/547.friend-circles-en.md): + + + +[backtrack problems](./problems/90.subsets-ii.md): + + + +[0198.house-robber](./problems/198.house-robber.md): + + + +[0454.4-sum-ii](./problems/454.4-sum-ii.md): + + + +## Top 题目进度 + +- [Top 100 Liked Questions](https://leetcode.com/problemset/top-100-liked-questions/) (84 / 100) + +- [Top Interview Questions](https://leetcode.com/problemset/top-interview-questions/) (115 / 145) + +## 传送门 + +### leetcode 经典题目的解析 + +> 这里仅列举具有**代表性题目**,并不是全部题目 + +#### 简单难度 + +- [0001.TwoSum](./problems/1.TwoSum.md) 🆕 +- [0020.Valid Parentheses](./problems/20.validParentheses.md) +- [0021.MergeTwoSortedLists](./problems/21.MergeTwoSortedLists.md) 🆕 +- [0026.remove-duplicates-from-sorted-array](./problems/26.remove-duplicates-from-sorted-array.md) +- [0053.maximum-sum-subarray](./problems/53.maximum-sum-subarray-cn.md) 🆕 +- [0088.merge-sorted-array](./problems/88.merge-sorted-array.md) +- [0104.maximum-depth-of-binary-tree](./problems/104.maximum-depth-of-binary-tree.md) +- [0121.best-time-to-buy-and-sell-stock](./problems/121.best-time-to-buy-and-sell-stock.md) +- [0122.best-time-to-buy-and-sell-stock-ii](./problems/122.best-time-to-buy-and-sell-stock-ii.md) +- [0125.valid-palindrome](./problems/125.valid-palindrome.md) 🆕 +- [0136.single-number](./problems/136.single-number.md) +- [0155.min-stack](./problems/155.min-stack.md) 🆕 +- [0167.two-sum-ii-input-array-is-sorted](./problems/167.two-sum-ii-input-array-is-sorted.md) +- [0169.majority-element](./problems/169.majority-element.md) +- [0172.factorial-trailing-zeroes](./problems/172.factorial-trailing-zeroes.md) +- [0190.reverse-bits](./problems/190.reverse-bits.md) +- [0191.number-of-1-bits](./problems/191.number-of-1-bits.md) +- [0198.house-robber](./problems/198.house-robber.md) +- [0203.remove-linked-list-elements](./problems/203.remove-linked-list-elements.md) +- [0206.reverse-linked-list](./problems/206.reverse-linked-list.md) +- [0219.contains-duplicate-ii](./problems/219.contains-duplicate-ii.md) +- [0226.invert-binary-tree](./problems/226.invert-binary-tree.md) +- [0232.implement-queue-using-stacks](./problems/232.implement-queue-using-stacks.md) 🆕 +- [0263.ugly-number](./problems/263.ugly-number.md) +- [0283.move-zeroes](./problems/283.move-zeroes.md) +- [0342.power-of-four](./problems/342.power-of-four.md) +- [0349.intersection-of-two-arrays](./problems/349.intersection-of-two-arrays.md) +- [0371.sum-of-two-integers](./problems/371.sum-of-two-integers.md) +- [0437.path-sum-iii](./problems/437.path-sum-iii.md) 🆕 +- [0455.AssignCookies](./problems/455.AssignCookies.md) 🆕 +- [0501.find-mode-in-binary-search-tree](./problems/501.Find-Mode-in-Binary-Search-Tree.md)🆕 +- [0575.distribute-candies](./problems/575.distribute-candies.md) +- [0874.walking-robot-simulation](./problems/874.walking-robot-simulation.md) 🆕 +- [1260.shift-2d-grid](./problems/1260.shift-2d-grid.md) 🆕 +- [1332.remove-palindromic-subsequences](./problems/1332.remove-palindromic-subsequences.md) 🆕 + +#### 中等难度 + +- [0002. Add Two Numbers](./problems/2.addTwoNumbers.md) +- [0003. Longest Substring Without Repeating Characters](./problems/3.longestSubstringWithoutRepeatingCharacters.md) +- [0005.longest-palindromic-substring](./problems/5.longest-palindromic-substring.md) +- [0011.container-with-most-water](./problems/11.container-with-most-water.md) +- [0015.3-sum](./problems/15.3-sum.md) +- [0017.Letter-Combinations-of-a-Phone-Number](./problems/17.Letter-Combinations-of-a-Phone-Number.md) 🆕 +- [0019. Remove Nth Node From End of List](./problems/19.removeNthNodeFromEndofList.md) +- [0022.GenerateParentheses](./problems/22.GenerateParentheses.md) 🆕 +- [0024. Swap Nodes In Pairs](./problems/24.swapNodesInPairs.md) +- [0029.divide-two-integers](./problems/29.divide-two-integers.md) +- [0031.next-permutation](./problems/31.next-permutation.md) +- [0033.search-in-rotated-sorted-array](./problems/33.search-in-rotated-sorted-array.md) +- [0039.combination-sum](./problems/39.combination-sum.md) +- [0040.combination-sum-ii](./problems/40.combination-sum-ii.md) +- [0046.permutations](./problems/46.permutations.md) +- [0047.permutations-ii](./problems/47.permutations-ii.md) +- [0048.rotate-image](./problems/48.rotate-image.md) +- [0049.group-anagrams](./problems/49.group-anagrams.md) +- [0050.pow-x-n](./problems/50.pow-x-n.md) 🆕 +- [0055.jump-game](./problems/55.jump-game.md) +- [0056.merge-intervals](./problems/56.merge-intervals.md) +- [0060.permutation-sequence](./problems/60.permutation-sequence.md) 🆕 +- [0062.unique-paths](./problems/62.unique-paths.md) 🖊 +- [0073.set-matrix-zeroes](./problems/73.set-matrix-zeroes.md) +- [0075.sort-colors](./problems/75.sort-colors.md) +- [0078.subsets](./problems/78.subsets.md) +- [0079.word-search](./problems/79.word-search-en.md) +- [0080.remove-duplicates-from-sorted-array-ii](./problems/80.remove-duplicates-from-sorted-array-ii.md) 🆕 +- [0086.partition-list](./problems/86.partition-list.md) +- [0090.subsets-ii](./problems/90.subsets-ii.md) +- [0091.decode-ways](./problems/91.decode-ways.md) +- [0092.reverse-linked-list-ii](./problems/92.reverse-linked-list-ii.md) 🖊 +- [0094.binary-tree-inorder-traversal](./problems/94.binary-tree-inorder-traversal.md) +- [0095.unique-binary-search-trees-ii](./problems/95.unique-binary-search-trees-ii.md) 🆕 +- [0096.unique-binary-search-trees](./problems/96.unique-binary-search-trees.md) 🆕 +- [0098.validate-binary-search-tree](./problems/98.validate-binary-search-tree.md) +- [0102.binary-tree-level-order-traversal](./problems/102.binary-tree-level-order-traversal.md) +- [0103.binary-tree-zigzag-level-order-traversal](./problems/103.binary-tree-zigzag-level-order-traversal.md) +- [105.Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.md](./problems/105.Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.md) +- [0113.path-sum-ii](./problems/113.path-sum-ii.md) +- [0129.sum-root-to-leaf-numbers](./problems/129.sum-root-to-leaf-numbers.md) +- [0130.surrounded-regions](./problems/130.surrounded-regions.md) +- [0131.palindrome-partitioning](./problems/131.palindrome-partitioning.md) +- [0139.word-break](./problems/139.word-break.md) +- [0144.binary-tree-preorder-traversal](./problems/144.binary-tree-preorder-traversal.md) +- [0150.evaluate-reverse-polish-notation](./problems/150.evaluate-reverse-polish-notation.md) +- [0152.maximum-product-subarray](./problems/152.maximum-product-subarray.md) 🖊 +- [0199.binary-tree-right-side-view](./problems/199.binary-tree-right-side-view.md) +- [0200.number-of-islands](./problems/200.number-of-islands.md) 🆕 +- [0201.bitwise-and-of-numbers-range](./problems/201.bitwise-and-of-numbers-range.md) 🖊 +- [0208.implement-trie-prefix-tree](./problems/208.implement-trie-prefix-tree.md) +- [0209.minimum-size-subarray-sum](./problems/209.minimum-size-subarray-sum.md) +- [0211.add-and-search-word-data-structure-design](./problems/211.add-and-search-word-data-structure-design.md) 🆕 +- [0215.kth-largest-element-in-an-array](./problems/215.kth-largest-element-in-an-array.md) 🆕 +- [0221.maximal-square](./problems/221.maximal-square.md) +- [0229.majority-element-ii](./problems/229.majority-element-ii.md) 🆕 +- [0230.kth-smallest-element-in-a-bst](./problems/230.kth-smallest-element-in-a-bst.md) +- [0236.lowest-common-ancestor-of-a-binary-tree](./problems/236.lowest-common-ancestor-of-a-binary-tree.md) +- [0238.product-of-array-except-self](./problems/238.product-of-array-except-self.md) +- [0240.search-a-2-d-matrix-ii](./problems/240.search-a-2-d-matrix-ii.md) +- [0279.perfect-squares](./problems/279.perfect-squares.md) +- [0309.best-time-to-buy-and-sell-stock-with-cooldown](./problems/309.best-time-to-buy-and-sell-stock-with-cooldown.md) +- [0322.coin-change](./problems/322.coin-change.md) +- [0328.odd-even-linked-list](./problems/328.odd-even-linked-list.md) +- [0334.increasing-triplet-subsequence](./problems/334.increasing-triplet-subsequence.md) +- [0343.integer-break](./problems/343.integer-break.md)🆕 +- [0365.water-and-jug-problem](./problems/365.water-and-jug-problem.md) +- [0378.kth-smallest-element-in-a-sorted-matrix](./problems/378.kth-smallest-element-in-a-sorted-matrix.md) +- [0380.insert-delete-getrandom-o1](./problems/380.insert-delete-getrandom-o1.md)🆕 +- [0416.partition-equal-subset-sum](./problems/416.partition-equal-subset-sum.md) +- [0445.add-two-numbers-ii](./problems/445.add-two-numbers-ii.md) +- [0454.4-sum-ii](./problems/454.4-sum-ii.md) +- [0474.ones-and-zeros](./problems/474.ones-and-zeros-en.md) +- [0494.target-sum](./problems/494.target-sum.md) +- [0516.longest-palindromic-subsequence](./problems/516.longest-palindromic-subsequence.md) +- [0518.coin-change-2](./problems/518.coin-change-2.md) +- [0547.friend-circles](./problems/547.friend-circles-en.md) 🆕 +- [0560.subarray-sum-equals-k](./problems/560.subarray-sum-equals-k.md) +- [0609.find-duplicate-file-in-system](./problems/609.find-duplicate-file-in-system.md) +- [0820.short-encoding-of-words](./problems/820.short-encoding-of-words.md) 🆕 +- [0875.koko-eating-bananas](./problems/875.koko-eating-bananas.md) +- [0877.stone-game](./problems/877.stone-game.md) +- [0887.super-egg-drop](./problems/887.super-egg-drop.md) +- [0900.rle-iterator](./problems/900.rle-iterator.md) +- [0912.sort-an-array](./problems/912.sort-an-array.md) +- [0935.knight-dialer](./problems/935.knight-dialer.md) 🆕 +- [1011.capacity-to-ship-packages-within-d-days](./problems/1011.capacity-to-ship-packages-within-d-days.md) +- [1014.best-sightseeing-pair](./problems/1014.best-sightseeing-pair.md) 🆕 +- [1015.smallest-integer-divisible-by-k](./problems/1015.smallest-integer-divisible-by-k.md) +- [1019.next-greater-node-in-linked-list](./problems/1019.next-greater-node-in-linked-list.md) 🆕 +- [1020.number-of-enclaves](./problems/1020.number-of-enclaves.md) +- [1023.camelcase-matching](./problems/1023.camelcase-matching.md) +- [1031.maximum-sum-of-two-non-overlapping-subarrays](./problems/1031.maximum-sum-of-two-non-overlapping-subarrays.md) +- [1104.path-in-zigzag-labelled-binary-tree](./problems/1104.path-in-zigzag-labelled-binary-tree.md) +- [1131.maximum-of-absolute-value-expression](./problems/1131.maximum-of-absolute-value-expression.md) +- [1186.maximum-subarray-sum-with-one-deletion](./problems/1186.maximum-subarray-sum-with-one-deletion.md) 🆕 +- [1218.longest-arithmetic-subsequence-of-given-difference](./problems/1218.longest-arithmetic-subsequence-of-given-difference.md) +- [1227.airplane-seat-assignment-probability](./problems/1227.airplane-seat-assignment-probability.md) +- [1261.find-elements-in-a-contaminated-binary-tree](./problems/1261.find-elements-in-a-contaminated-binary-tree.md) +- [1262.greatest-sum-divisible-by-three](./problems/1262.greatest-sum-divisible-by-three.md) +- [1297.maximum-number-of-occurrences-of-a-substring](./problems/1297.maximum-number-of-occurrences-of-a-substring.md) +- [1310.xor-queries-of-a-subarray](./problems/1310.xor-queries-of-a-subarray.md) 🆕 +- [1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance](./problems/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance.md) 🆕 +- [1371.find-the-longest-substring-containing-vowels-in-even-counts](./problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md) 🆕 + +#### 困难难度 + +- [0004.median-of-two-sorted-array](./problems/4.median-of-two-sorted-array.md) +- [0023.merge-k-sorted-lists](./problems/23.merge-k-sorted-lists.md) +- [0025.reverse-nodes-in-k-group](./problems/25.reverse-nodes-in-k-groups-cn.md) +- [0030.substring-with-concatenation-of-all-words](./problems/30.substring-with-concatenation-of-all-words.md) +- [0032.longest-valid-parentheses](./problems/32.longest-valid-parentheses.md) +- [0042.trapping-rain-water](./problems/42.trapping-rain-water.md) +- [0052.N-Queens-II](./problems/52.N-Queens-II.md) 🆕 +- [0084.largest-rectangle-in-histogram](./problems/84.largest-rectangle-in-histogram.md) 🆕 +- [0085.maximal-rectangle](./problems/85.maximal-rectangle.md) +- [0124.binary-tree-maximum-path-sum](./problems/124.binary-tree-maximum-path-sum.md) +- [0128.longest-consecutive-sequence](./problems/128.longest-consecutive-sequence.md) +- [0145.binary-tree-postorder-traversal](./problems/145.binary-tree-postorder-traversal.md) +- [0212.word-search-ii](./problems/212.word-search-ii.md) 🆕 +- [0239.sliding-window-maximum](./problems/239.sliding-window-maximum.md) +- [0295.find-median-from-data-stream](./problems/295.find-median-from-data-stream.md) 🆕 +- [0301.remove-invalid-parentheses](./problems/301.remove-invalid-parentheses.md) +- [0335.self-crossPing](./problems/335.self-crossing.md) 🆕 +- [0460.lfu-cache](./problems/460.lfu-cache.md) +- [0472.concatenated-words](./problems/472.concatenated-words.md) 🆕 +- [0493.reverse-pairs](./problems/493.reverse-pairs.md) 🆕 +- [0895.maximum-frequency-stack](./problems/895.maximum-frequency-stack.md) 🆕 +- [1168.optimize-water-distribution-in-a-village](./problems/1168.optimize-water-distribution-in-a-village-cn.md) 🆕 + +### 数据结构与算法的总结 + +- [数据结构](./thinkings/basic-data-structure.md) +- [基础算法](./thinkings/basic-algorithm.md) +- [二叉树的遍历](./thinkings/binary-tree-traversal.md) 🖊 +- [动态规划](./thinkings/dynamic-programming.md) +- [哈夫曼编码和游程编码](./thinkings/run-length-encode-and-huffman-encode.md) +- [布隆过滤器](./thinkings/bloom-filter.md) +- [字符串问题](./thinkings/string-problems.md) +- [前缀树专题](./thinkings/trie.md) +- [《日程安排》专题](https://lucifer.ren/blog/2020/02/03/leetcode-%E6%88%91%E7%9A%84%E6%97%A5%E7%A8%8B%E5%AE%89%E6%8E%92%E8%A1%A8%E7%B3%BB%E5%88%97/) +- [《构造二叉树》专题](https://lucifer.ren/blog/2020/02/08/%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%93%E9%A2%98/) +- [《贪婪策略》专题](./thinkings/greedy.md) +- [《深度优先遍历》专题](./thinkings/DFS.md) +- [滑动窗口(思路 + 模板)](./thinkings/slide-window.md) +- [位运算](./thinkings/bit.md) 🆕 +- [设计题](./thinkings/design.md) 🆕 +- [小岛问题](./thinkings/island.md) 🆕 +- [最大公约数](./thinkings/GCD.md) 🆕 +- [并查集](./thinkings/union-find.md) 🆕 +- [前缀和](./thinkings/prefix.md) 🆕 + +### anki 卡片 + +Anki 主要分为两个部分:一部分是关键点到题目的映射,另一部分是题目到思路,关键点,代码的映射。 + +全部卡片都在 [anki-card](./assets/anki/leetcode.apkg) + +使用方法: + +anki - 文件 - 导入 - 下拉格式选择“打包的 anki 集合”,然后选中你下载好的文件,确定即可。 + +更多关于 anki 使用方法的请查看 [anki 官网](https://apps.ankiweb.net/) + +目前已更新卡片一览(仅列举正面): + +- 二分法解决问题的关键点是什么,相关问题有哪些? +- 如何用栈的特点来简化操作, 涉及到的题目有哪些? +- 双指针问题的思路以及相关题目有哪些? +- 滑动窗口问题的思路以及相关题目有哪些? +- 回溯法解题的思路以及相关题目有哪些? +- 数论解决问题的关键点是什么,相关问题有哪些? +- 位运算解决问题的关键点是什么,相关问题有哪些? + +> 已加入的题目有:#2 #3 #11 + +### 每日一题 + +每日一题是在交流群(包括微信和 qq)里通过 issues 来进行的一种活动,大家一起 解一道题,这样讨论问题更加集中,会得到更多的反馈。而且 这些题目可以被记录下来,日后会进行筛选添加到仓库的题解模块。 + +- [每日一题汇总](./daily/) + +![每日一题汇总](./assets/daily-summary.png) + +- [每日一题认领区](https://github.com/azl397985856/leetcode/projects/1) + +![每日一题认领区](./assets/daily-board.png) + +### 计划 + +- [anki 卡片 完善](./assets/anki/) + +- [字符串类问题汇总](./todo/str/) + +- LeetCode 换皮题目集锦 + +- 动态规划完善。最长递增子序列,最长回文子序列,编辑距离等“字符串”题目, 扔鸡蛋问题。 解题模板,滚动数组。 + +- 堆可以解决的题目。 手写堆 + +- 单调栈 + +## 关注我 + +我重新整理了下自己的公众号,并且我还给它换了一个名字`脑洞前端`,它是一个帮助你打开大前端新世界大门的钥匙 🔑,在这里你可以听到新奇的观点,看到一些技术尝新,还会收到系统性总结和思考。 + +在这里我会尽量通过图的形式来阐述一些概念和逻辑,帮助大家快速理解,图解是我的目标。 + +之后我的文章会同步到微信公众号 `脑洞前端` ,你可以关注获取最新的文章,并和我进行交流。 + +另外你可以回复大前端进大前端微信交流群, 回复 leetcode 拉你进 leetcode 微信群,如果想加入 qq 群,请回复 qq。 + + + +## 捐赠 + +[点击查看完整的捐赠列表](./donation.md) + +### 微信 + + + +### 支付宝 + + + +## 贡献 + +- 如果有想法和创意,请提 [issue](https://github.com/azl397985856/leetcode/issues) 或者进群提 +- 如果想贡献增加题解或者翻译, 可以参考[贡献指南](./CONTRIBUTING.md) + > 关于如何提交题解,我写了一份 [指南](./templates/problems/1014.best-sightseeing-pair.md) +- 如果需要修改项目中图片,[这里](./assets/drawio/) 存放了项目中绘制图的源代码, 大家可以用 [draw.io](https://www.draw.io/) 打开进行编辑。 + +## 鸣谢 + +感谢为这个项目作出贡献的所有 [小伙伴](https://github.com/azl397985856/leetcode/graphs/contributors) + +## License + +[Apache-2.0](./LICENSE.txt) diff --git a/assets/19.removeNthNodeFromEndOfList.gif b/assets/19.removeNthNodeFromEndOfList.gif new file mode 100644 index 0000000..5e13954 Binary files /dev/null and b/assets/19.removeNthNodeFromEndOfList.gif differ diff --git a/assets/2.addTwoNumbers.gif b/assets/2.addTwoNumbers.gif new file mode 100644 index 0000000..c061d7d Binary files /dev/null and b/assets/2.addTwoNumbers.gif differ diff --git a/assets/20.validParentheses.gif b/assets/20.validParentheses.gif new file mode 100644 index 0000000..3f18a0e Binary files /dev/null and b/assets/20.validParentheses.gif differ diff --git a/assets/24.swap-nodes-in-pairs.gif b/assets/24.swap-nodes-in-pairs.gif new file mode 100644 index 0000000..150620b Binary files /dev/null and b/assets/24.swap-nodes-in-pairs.gif differ diff --git a/assets/26.remove-duplicates-from-sorted-array.gif b/assets/26.remove-duplicates-from-sorted-array.gif new file mode 100644 index 0000000..cd18544 Binary files /dev/null and b/assets/26.remove-duplicates-from-sorted-array.gif differ diff --git a/assets/3.longestSubstringWithoutRepeatingCharacters.gif b/assets/3.longestSubstringWithoutRepeatingCharacters.gif new file mode 100644 index 0000000..e48a4af Binary files /dev/null and b/assets/3.longestSubstringWithoutRepeatingCharacters.gif differ diff --git a/assets/86.partition-list.gif b/assets/86.partition-list.gif new file mode 100644 index 0000000..61ce8fc Binary files /dev/null and b/assets/86.partition-list.gif differ diff --git a/assets/92.reverse-linked-list-ii.gif b/assets/92.reverse-linked-list-ii.gif new file mode 100644 index 0000000..2a49274 Binary files /dev/null and b/assets/92.reverse-linked-list-ii.gif differ diff --git a/assets/94.binary-tree-inorder-traversal.gif b/assets/94.binary-tree-inorder-traversal.gif new file mode 100644 index 0000000..b551f8c Binary files /dev/null and b/assets/94.binary-tree-inorder-traversal.gif differ diff --git a/assets/anki/leetcode.apkg b/assets/anki/leetcode.apkg new file mode 100644 index 0000000..93f892f Binary files /dev/null and b/assets/anki/leetcode.apkg differ diff --git a/assets/daily-board.png b/assets/daily-board.png new file mode 100644 index 0000000..5ed02f4 Binary files /dev/null and b/assets/daily-board.png differ diff --git a/assets/daily-summary.png b/assets/daily-summary.png new file mode 100644 index 0000000..0ed6581 Binary files /dev/null and b/assets/daily-summary.png differ diff --git a/assets/daily.png b/assets/daily.png new file mode 100644 index 0000000..3225ea9 Binary files /dev/null and b/assets/daily.png differ diff --git a/assets/daily/2019-06-27.gif b/assets/daily/2019-06-27.gif new file mode 100644 index 0000000..c38b47b Binary files /dev/null and b/assets/daily/2019-06-27.gif differ diff --git a/assets/daily/2019-07-23.jpeg b/assets/daily/2019-07-23.jpeg new file mode 100644 index 0000000..f8428c9 Binary files /dev/null and b/assets/daily/2019-07-23.jpeg differ diff --git a/assets/daily/2019-07-26.jpeg b/assets/daily/2019-07-26.jpeg new file mode 100644 index 0000000..c112723 Binary files /dev/null and b/assets/daily/2019-07-26.jpeg differ diff --git a/assets/daily/2019-07-30.jpg b/assets/daily/2019-07-30.jpg new file mode 100644 index 0000000..2f298b1 Binary files /dev/null and b/assets/daily/2019-07-30.jpg differ diff --git a/assets/daily/weight-ball.jpg b/assets/daily/weight-ball.jpg new file mode 100644 index 0000000..c245e7d Binary files /dev/null and b/assets/daily/weight-ball.jpg differ diff --git a/assets/donate-weixin.jpg b/assets/donate-weixin.jpg new file mode 100644 index 0000000..b29abd8 Binary files /dev/null and b/assets/donate-weixin.jpg differ diff --git a/assets/donate-zfb.jpg b/assets/donate-zfb.jpg new file mode 100644 index 0000000..d2db594 Binary files /dev/null and b/assets/donate-zfb.jpg differ diff --git a/assets/drawio/11.container-with-most-water.drawio b/assets/drawio/11.container-with-most-water.drawio new file mode 100644 index 0000000..36b6571 --- /dev/null +++ b/assets/drawio/11.container-with-most-water.drawio @@ -0,0 +1 @@ +jHzXkqRIFuXXzOOaocUjWhMBBPINFUCgtfj6xTOrpruyZs22rDszkgDccb/inOPX+Q/KtYc0xUNp9Fne/AeBsuM/KP8fBIFRmrp/gSPn9xGKRr8PFFOV/TrpnwNOdeW/DkK/jq5Vls9/nLj0fbNUw58H077r8nT541g8Tf3+52nvvvmz1SEu8r8OOGnc/H3Ur7Kl/PUUCPnPcTmvivJ3yzBBf3/Txr9P/vUkcxln/f6vQ6jwH5Sb+n75/tQeXN6Awfs9Lt/Xif+Pb//bsSnvlv+fCzjkNLcrfMVTytYPJi6QIvg/v+6yxc3664F/dXY5f4/A3e8BfKzar6Fit3xaqnuA9DjJm2c/V0vVd/f3Sb8sfXuf0IAv2Diti6lfu4zrm376uhX6/vr3r3swTVWAa5d+uI/G8/A9he/qyO9es19NMr+PQr+P3J+zeIn/gzLffyLi0BX/QbjKYx/2DmlS0TP3P9NxS8Et7k+ncP8Q7/M4Jrw/sTmdRtn9oWCERrA8G2Pyk+ZIZvbg9jZPEU2el0mSnXpcVoULq2XxEqPeX5TCJOC8UTls3eOOdrDMZlm1p/IiznD9R9Mt4T8I22us5WrHLjjVfY1gMI6meErlxgs3FMweMo5VFKqJuYrylut0JqP8JLP71Pj+/010eAfPV9TQLUlfbReBL654y/IRzrclmQLjgm4jE/MF3eL11VeF9qDi8u44SycDbURy7ftRFo2Lf52QqtRc0GriW6GWyzbL+0JPsRiGE5R7JLh7FKzy/qGbKs6k8SFUfS5V1lSmtV5YvoWUS9UoI3uPFSP8cV2R1VVqXCanC0ydlHuFfc7AP5SRK2iJ2bkgfcmJCtcyB31fAiahBA3aFTNnafn8sDpHbfwJJaiPobxunrMjsn1nUbQxonkAV/9cY323LzEIx9tS+bjixZmtmvb4rcSCQGbZWd570g6tymYj2zyC4/GMLAO27yt/PKtSQUxhK4N0zJPiPEsxemYkdnzesX2RpxBs3YOmoomlHPkeLcXjrDwdaqnWWOiJW31yD7SVsj/7ZqokYwSZ0SDeJ22r25Rc1veGeJ62dZ1nXzxyGS4olYcbBps+R8M3zN/PxyqYWiZWczJ3KyY3bncPVnIjnyT6SEHDIT+j022gW45fmFv6rJIJVajJ3ZqFPv0/ZlYzVZQ3dEc4lCTbZX6ATgEjCMd5zVfP/tE6uFYtNmZ+p8Kk+K9tp+FK5Z+F9efsS3FiaaZwT+OTT3JtuPv1yJLUSp+W9dNOLAGpXO4p8BZmYZyv/Ojhx5AxWapKFvXVO1WI+NNSfvbpgS+MAr0UnDsCX1ARum5UEvj092x+nYgP3+eokqYK3cGpY7vzxQ+LfdcFJBY2VlGf7h5f0aAZ5adtWOk9YgIH/FjDw3fD7cBlZsy6zof0x0iB6x4xb3GmcEp9r2KBGreBUuzMT3tvPxDzscMihQ6hnNH0TNNfA/XPeWFZsx/bKl+0QF6UJ0g/R2GG8mJ2NAFJxJZEXtWXc/xrBBSeMRn3dduh1TO78NMSZmkXhVOPy4H76zsmZHsONP7omBQi3vj98FzB/3F3hmMR52n5rFRJiaL8HLXC4CnBr2bOEByjozrpQrg/R4tjbs91yieXCwL++Gn5F3e3yR59b6GlcW5nmj1D448WELMWpGq5LwdOeX2wg3j9GVn25+7EiqVo4VB9dE4KT5E7Bm5nftpBUpcgAhh1/RJeqlrK7l9WyWPibZV8GQXxfPfs1akEU/8dx3rIUmwlpSKQpZ53NmUVVrX2v3zwesWMrbSW01uW0gysqo0H/8/YgruyxbMoIK9/FS2PvdWjon9YyB1THda+R0g2je55Jv7d6T+iEJgCgZMvCu4cYf/33N3nGbtQpLEmmET4LJ8dv6fqz/l92WY/MR/WCNBXd7ren09735817hlouJaFJ3XGMr786T1qoixMKOSazx9pkdZY9z8yzWUTzKFUtkRASn1VhvlnP776Kn/3FXsZ5W1LOvJnjLpNXVW+5icXLh03EuHx81numXAkhlPb94Vr+V/2zoWKKcYCpRJW+wIO/hCKvzymrW+vVW6vzR8F5t0Ig/XhOH0d/V8xo5Wh23+Ysj6qSPZX5xTFtDmI2XxImKmcT8W5m9AnUWfGnUJ52/Tuv1NDOx6IUxb/nmU+5KxXjNCymkzZVGGrnm3vLKMXz4PhBoATElkINCDoOorwo3yhneYS14uIynZF9elomX2WYzLxMZYK9vt8sYXfvlgoCOmZuG9lVZj1a+LV80ig1mvPCq/43OZYfTTHoLXYP3hVbyRp9XB/eqoB6On5ovIENS7RqTwHajU4cz48OUEyqevGZvgjcpKdIztV/d/5oUy8UI4XyFi3ta54k6IEGEHkjtuFY+q9HipQlMXM6kgSQuz2dbdDQK7YayD0cko6n/dvQnjfPyuZBFN05z8xIfI8HyfgafdfUOFwNojkml6Es7LxFTZ65vsOjl61aecmwzX0IDa4txhVGBVXNhV9tD022PdzPq0jJnLnTrne6niDX/S2d2lewUo4JOiKTp0ayky67iLuSPiO5sl45/oJWivf3iiEIBLxOIu/z3zX3ht83VM7y5uSnI+GzonnfsFP6c54D74Xzs9mttsMnxR4JLGS30d54z32bQDI/vWfWQhJd8/ytvBgnov7J3uR6NeXwKCD4DqOXNB1bFWKdegpazX0ItKIU/tgnCrGXuOKsnWktlxlifcYhbxYJF/BKGYmorlxhiw9EWRQs/j0hFiH9dRtmtovFc+Z+yPs1pbn7/BU+4e9NqDF1sHESp0ek13cSG9o7Ry5Jph8jxb0ba8z1AjKLD+zyoBR7zmbckUfwwF6/iQ/fPW+nbu4YYEK67FAMAtNPVh2BXYAaAZ7yGAsbJlMFV3f9ypcvp/0/U5imCT9abpOB+CndeVE1wp4EVZA3tZKDhsSG3UOVye5wjLk5B5GNn+6Uh+LlV9syUc8uU2+kn06Q1Y8IEcQxq6xWbWgkOotDu67iMVFeQhVG/jwh5lbN4UobnD1fAXk804xuuArptDkZrUcyuF+Kqx3X1GUKIPsEB+mHu8mw9vuv7MHBV2MfH2IhKQB7H8lokXaAXFbLqsLd3YTO/qFvsmz8UdozlmO3XGmc8VKOpO1rGb9pJLnO02x+8wsTdMsm/th2F8spfrb2/Dv21AhvCH/mItByWGs6FbQvJAD0zjQna3ChFVgekeu5kmDzKnyaMYTEnhx9kW3vBJb1sdT63EUM5Plni6DV+3F3BpK6wS3XSafoDe4oYa3K58Hd3labJe+52bvMBzFcy/CKuzoUUlLO6iWpj45Nnz58A6BEeNWQzima+tunPArZ7PWPfZ3v8bBBJRF3IFPezSnVCHCofCj71i8vhkPS74//IWh43xAnMBXJ6uqHGA0wwJiuUhhzHvxgCvAHmTqE/oRNeAdVu8+EXjKAViOL13aNGi44GVlAStkbmgfJlUpdTi8W26APPwgOLBV7DC7XivLMAXRVrYx6HRV9SWVwJ85VwhqGShKU9hs6NewIizIp5ebm1GJ6KT6z+tG4vp984i/bgPN7jCZRaq/xNPEZUMYxfLaT+ZjUXyq3GyBmKn5VE1daj2LXP0p4RtT917Xhcr1AJ5LSx5iOnxyryfSx688O/OGEsgoDPvyKd1YTAzVMozGQ7ayBd4c+eq9+j295+2aQdQkZ8AQBfU6ysWfastiOBvrmKOnHPNTWJXlqEWf0qQk8sWbJ2Dyxtks/FQAtZDarrMjoaAmjRrFe340NOyKZLwZ4Ml9mxkFrDnpgnce21/px8g4yU4rpKfkKuJMvWbzpisXptGKKt7DPPURLyRybYIvpzAlZNGQvT0aJL/e02QmYBKlYNkyBkWTjjmX+OnMDh+jznNRz328rGGJNMHT+72hI5+JojCD/XBe9AqNRmBFTRtdkCQkOnJQhv8Vhz5rXMjkRGfrlmPRE0SV6YVvaXBuJlmQLkPYOE/bvut1Lawc0gfQ9Mpa0srmMeljERxyw/jgPpps4OKYyO4YFGHh501rIYq8HzlNXvsRAD9+0PTQwDgRv6sKRa/rChLHcXLR+GC1c66EolXmfSdBKyss0FahPPZcrY4sf/KjjqPhAlnAOrnRjazSv+Na5LvvKIHOp27sfTWHPJOwnHhCIMM+cXrGgpXxl4RIj/xmSmqTKWik3yF6Cp2TVLlppdoXbs5x9M7ea5k7cORfst7cBO03/pS/UhXJ8DBEP2fhmVY3dXx8sEfg2cN1BxfRcm8uyGB0YUtD7y5yPwtFnBRexuzTlzfdT2R4bKIeGNZ3yNjk9NmRC4ySJr0C3SIh7+hVLxhXCaUQgmnKpFUqlcJnPWjGPioYkre+BFXq4iDO+5xbMQSv5HgJYuTV236YhcPZ3WjHNMdRdMizPsmmQttl2NN52uH5jhGZsj+USIYVuBTPzx5ZymBYjZmrueeeJDyHRssNcz6WPWeOw+O/LOTDytY7jLmDNpRaL1jx5DXAqGeVPm5unIE0HQp9sz0nGwaYQn5VlkUTkH4DfatcQIxRXt1RAZ1FHk7M1flXDJ7K8qQGfKkV4KqSRrhKTZy0qG1OKKq8eoaXYMt+PAq7km5V3xkrLPftY5yZHfpwc1qVfuP5rkWGUtpyAcGeyCM+OKb4kx9cBcgXgsPLQ9SHWnrHXNalyDF6fIEDioi+pymOPqozqkKUeGbYWnQdCsrjNXRdho9bU4W69ZrKDHv1lfvsa/mB32BQUEbEj8Mha8xEVm19vdR6lHZLuu8ne2X1AErWwxtaPDrpam8yY7yhQ8vAPEl+CqyjC+UPrjDrkBBvq3Bqw4F2XYd4Tdf5frzmSsmjrbK9J2/a8Re9kVF33xuJ3nlKoqR8uxOwJnmTFHt/lcpLBvGnxCfOlmDCfezLlJMLH0kWGxrbm7UBNGQV1PbpG2fKu2escjXCblTDJmv61IMIF4Xr5QhkoQw5ZGN6194S33iWIf5ksOxDgdmkFIqZNEdYI0+XktwtxxuaEIw319JahmyKA38h1ZVLp6bdTA5/4Y4nwOUajD4KPIGUX8+B/UALpRqOSNr+Lkg28ULX3G4LCCrwcFbjFsyVkEk7GblurdXjR/YdVo51GZaEepOOtjBpiOF+qF+yoBWL1V09zBg5pTC7/FiWBF0ToAcy8nMvLu9GmiV7Mw9+jFbM/wCovIpj8AmwdWvgZQBT2YWO9oROrtq4l1gvTuhWd2TGniYw4WLgrVXrtLDR2i9xx7pe6w0MoDKOJTZkxmSCaU9VlD85kCRwi+sr3B5Fj5f9KTPCf293Zt0zvZwSvOaWpUEfBNVang/4kEBn4XSQccxy1l40tBRviHUqZmPhsdHziNQUp8q+Mjx6CGLZtvbLK1ntDguL/XhMSilFO7TIpPBx1HNF7Hz8k3ffHF6R4wyT15WQSxcdpzV1JJ7wTha1o+d0pjUs4XSo6bfzioE2SzDJGQW72Qc7ZGaZCaMxYLGm6rv6TE4l1UvyTvxsd6SCEY5dEKZ3YnuEkHuOBaX9D/6ZWcihV5W4Qy+lN5OQWuQTt25m0iVO65uhvYtM2Q/Nfkk4pigEZANA6SnGo+ShFzNeAe5x4afML/KkKl/co7wGNOFKKVUmWrp8NZ7Evp7G0SItojtb8thHOmLTP7WlAqrSZ/Uh9jDtnmtkPlgL33ZLZcuzJh93GHiVp82otS0x7g1n448y4oryeRkuOZaso33cWbJV9opsEoKOOKaQ1syEx3j8qU9RkFnkJkBGA4eV1jBBccDMP5kyImJfSpPUjzAUcAuZekz8406AP0KObvmsXG2VKXP7iyysn6qac7MqUapYxtywkdc5SanVQon/1hKehRsrtdCmlcqqyv/QXd5FAbm9pUwAxOKPC2ADwvqp9liF8q0MOvJTMnxhePKLXGHYn17wpUX25rcWaQxhLRxrCDDtT8VAAoqBWz605En3nQi5xk+le2F6yFVeVTsBzf6oLepPzVmRI5VmQyHVda4FoAXXFPlnO74CM7eJKjspAgpBfv7SJ/fH/rKUF+AVbabw55/PovDsi2X7wRqKLCaSR2/3MkJ9XhMEAtjI9N/3GW76JB2uQtQapa6H4LQIfHIFRTKEeMHRtEZtU7+TdF4heQjjb/b+GLfJHFHvrb+9t4yrjS3FAJueqERq1h07zjdEY0/lEUH7bbAaIPxIhJ6kY/xSt3z1zRwPmnLD1iDuZJBQqjQn8aZCVos0NzihyXgsNxUVBeHcHs1svjQXIaCb6TaI7etww86EeekTGJtIvLldPMJlm6PCMD4tP57oBdL5OP4SXuTVbrSZANhRlMgMpCnIPyfDUet5MVanSi4/TP/RmIwQgAMR9R8LavFelOpsl/Ma/orzwYT2yh/t9YMx+8tRYsLkM93p2nJGnrPa0E2jtnUzxlHkS5PTfqJ4KDAQVB6vp086HwJg1KkZc9HOkyOwvpVjg6zGxPFCOgwCgEi6CsTZyhvGiv+l4V930sOcZ0pwyYMiFHLirL5Oldlx9uFcKF0833BScGIFEFqOvJryMzv2sAbZW/auBOvmm350mRKbSU/pMZ76ALwNUBynWX2yD1XgqhT1XjZe9WOzFPCIRfwMliVFWDq8RKum5PH66CQNaCFRvK3Kl3ehOoSbuOz/VdVfhURymfposKQu6Sqn1vl5Rk7hUs3dhh4R6JW/xUfnwUXl4jDRj4cxcsxFQxc37Z/RLwnRWuv6AQLSdTheBnSDJbQU08UnHVbpwSJHubeRknxyzSqh2JuApi12z0dVZ/Z0PCSXxQQlzjg5daynmsmBDzI/arefUqFvzt2H32pyWDHJzFPoDLgJk+om2gp8YOrjpzqHd7VSC5/6ZInTUTiEOS3lXyMbMUuSipt8TbgvCN7HG5Xz5g6B+Ep77LFk0DkVFsdj0RoHpdxGUyUDixtX2MyhLf68Q+qpP+nm6XJwEDUXOiocn3faoZSbdu6PiJLt/TcmYzCkeFFdS/cAvPI7+uIEGKPrXN1aKbVunj6yCv+BAhHXY/hkRsEL2LGdRbTZXwpd70OCjImJ5kwiV7X24U+jVAbB4QjephFVXxHYHdwotKq4w1U4TQ7r4NuE5B5VMjg0WDFToiUgHxD7K8ZfTlyoNEfQ0oZ0AGPSzaXUxvoihTe0N+b8cnD3/QZgW1oQAn6U3dI3IempGM2s/uziN3nq0/dVp+beD0BS4h56QQPRKtbX7oRxwG+9C3IddgzzAyu/MDpkk2nYsCvVwUTSNKu43RNX5Eot1lTj9/kMRIepclcrvceTk5k8ZUmDQBvrGTLxbdjD0Q57yzCqCvxR5Knx6t9AZ01jw0/oAUKWudMXeWEZcuyBmPPKKN3T9SonE/lNrgvkwhPuTOk4oFqzkGp/c8mt3Z7ehzKeedQTM8y1S1EeA6qYd7w12P4NP1JSn+fDDTohhSaL4nW/HIOG3CIc7+Zx+Ky0TJzzpBEi0bE5apNDxCrT2BJ49OpHgKH5ixeNI+aAosu7uTMdpteN2uoyHyF9AMaDyfOVZA5LjcDQEWdJwUoQ4LzCgrzH1JhnMipbzehhT0syw93iqG9z2aVR7jCfgHoA/Msu8JuQCZ5ABtzPEFuW7XEImnD+OKj1DjK3cfIsUA0Z/tjBFU5gzhJx566tXbBIKcSb4ViXQCErHg/N2XxIgtmXwvXwZ5MAji78UsBYAvRveLXG2NOysLxxfltX4/EtIi7+5o/vlxT2xjsTAvHIeUxlrEeOALWKvVwXokylBt2WSyXm9lkPk3kV9L6PHx2wAzLa0OtLwQOximRQRhW39AklEXJbvO5AS9kCecYO4Kulykc8Q4SI4kWtuV7jd63ged+M7JHPIxoK3/EicFUAtyb11CvSpji2zu1hGraZOoHFPqT3pxpp8ZNRdvAVtgreOdI3oNQpwO5PwmDo5QBDrW+a8hSOgAG2zWIe3BLoPT+wmkuJeiZ0OyanBIvmRMJq+pDYxKRjy2O6njbwOsVWFnkPsGmhYQxvp5OgiPGpAIZnDf39PcToG/lO1tS1UkIYizlRz9vs8LYiScA7BXi701n2fsoi5msF6eCMZ1KboAzee/stXYuO6PsnzqhyBDGenFGM80Fq6jMGqhXulqOhlLA3KTxl+s0PBiaphGDCEaDSzG9mZpQvnFYeuoWDJQXqLQJz5U13ti5cCuqqNsJ7pFjoubA7iAW072QmrfeWhgKtBZKGqGvRla4vlCfkJX0++vb6SnFo0hN2tj5xvfhwuO4j3ouA89iqso6OOMgRzWTM8TKr571kH30/JmQbCaiNu3gQ29eTgiWrjt6Fe3VZrqOrrRB48GbUdwPpKe2PWV/Zil5QIcgO2xP1gi8RU2Dj8z1/zj44ogdOXt0jFZRAfjzyLFzo2Yze+1AytpQ8uv19p6AVzbaYS32DGlDjpSat/EagfOJkHAu8J+WyjjTImANBZkpCtxPcj4Z1On7Qe6GY68IXzy8Pb4nrUPbh2UKe2Xb5w+GePeyKbPzCd2CrUmTwBDTL+Ks6+Ny3s4SSUSBBaotc8tfh+c+22dR6m7rjCUzWueBYW6AinU3VjWAJKQuN0MfJO7R2GT9XljQTPFOz7jsDJOOQ3oQdEQ0dxvZJUS5cPMWYL5BSrVtLEnmoRRkxHGXslyT0Hf6Rh/utk9+x99sV1w6BFKDdmGIx4gAXljsBAgMufFucyty5M7K650NQQ6qoHc4OA6XseyK7k9On67qVM90AT2rYnp6wigtVemhqM8miL9UmGKmKL11ADOm1twJ0iiSxIiKz8UEkNeqyLJ4d80gZnOIeDybVNkvMOtKf5fFxs+Mjem+m94yF1QEB1D+3pZRlqHeHDw5WwVbzvbE3H3GIIbhuPqFjZNYeGwm4untSau2BuPK+aa9S3THK6ejSsyX6utE5KfIsdQKdYTyaDogfub8huQ8QbEMBfhi3RRn52dBw2LcPS+72fI5v6GU1lVogLxCjw/r9PHEX9c6LmK3RgbmDS/NsdTn8MU4TGQfb5jR0f3RPzgwteMcIpsVATYzCDBqB6MV+jyhYpzAPKxpHk2Kt+snZPEeyvMLtSKPhdEsEa3vBagVt/ZnnBIIutwkHpNRT3ehzoN4jen0Yy7Iw/iqe+FpQjjg1YJEgAtrcjUIDvcBlE3JK3cxHqPX9gSnGMLqfk0WtDrfbAwXyUfakXqjEjRMwVtazPOELX0bkTS6S2D65HHpbK97Ae1e3DPYG4/cypzI4b9SK+0j/dEEm+R6vfkO7gCSDpiCzKFidvQjpKsqX8ksZnikAMjKTUumZeb+xMyLb7rpkDe9Ey7feFFSOMMhpDyX1a3tEKVgPjJJTXlLpcyr7LNFTSDpwrxzvGhhGfLskegRabUMxR+aIYP+I16rxnhlo6XoFAQJWHKgTzPYdqsmrbYWospfIz229b8WcG7I8H5ssnetmBxRKsZ+GcjJ9NWF0AWtgKdV64i6xYDcBAWIRTMMIhcKPZCHYFPpVjSKzLmd8COyVkbutVL3HhYunXc2Ae7hlTiqKkuSE4HR+YtTcHlF1OnpqTUnH1XhOPzSXhTZUDrYXQlKgERIG8zQNNJXA0vt6s+Je2PJNh+xiirXdW0J1q3QNOtT0U9mG2Onkt/+Sy537yXyKAPW9YEAfDPARKVp9zP3otZzLGTG8g0ITObVdqfWl0tbxxGFEl3i17WPLIxwwuwoMBQXZXjxoJbHlSiz6QNyG00HLfhl3FKSozswW9cZzbodS4xNYxlz7rabbE5ACEOMr0hTXO986PYAxIIS/0uQak+Z1uW2HXDeqSo8ShxXaCDQKVZwqF14T/SKK86K4L9y+TNsSYxlyIfKTsH+Vz2iHaek5myefCEgPzy6dtEkrCL7kX5RTUd3FnfAlR+gl43tCU7P/YnWDNFlvgZi1QBuKR4etfqG8LCu4TTuqDoZ9V6YsX5YMrpFndeUvtBKEtLtpS1Hb5tMZZXQnoelYHEeYKiUShyga8CV6JNVKrwRMUwxOHk+d+YAsApE3hUFyfBUXYtASodqTK8pFsCIAFIAs8nleN5/+lswBr2hXGWR6CHyP1yxMAfMvamYSoUMDMqGkMyXyUI8nIEH0BZPHteXhO6dJFF3WlKbJjglvpCj5ruUG+o5tmStU1pw0kSDanPygyEngHdb6xDI07KqaPkmL0ls2SzUjGoKuxLGdMLa3P7Hs3PJ0ZEpE/KqhaZdo7HcV44PrBZM3O8iRboosu+/c07BrXWT70+DpFWm+OtkOHgVdwM1wGOnoeYz3tBYZLF/6x0TGMeCRDAaVQda8uZapWLvqveVJKhP0iGeQOHTSxfSofVONzNpxDgi56OfVbYbTc8Ic5TWabNNs8QZIAjzvD3+5fGNCNrKeQq91jmOwbpiI3O69evs0dr4akmRJDZHVbAsmd5VRvVI1dF8m8Tmo6ga25/oEXjJK/K9V4llqJJDDbkgXTNqN2EofHIYI4huWoS1Bkq+I3I97Frg8TQ3Nn5OyRwKF96Q9ZALkszzu2CtPWs25CwRpv7QnKbO/FrVZOph9Dosb0RVv/DsRUgsH/TN16sHuS5mce7K3A73EQ7+E9QrukYPDqkSTyXhpQwqiBe+5LhtIBYOzZyuofHBWy3+MT03xvLIo7MjstEym3awiEoN/p/OjeRvpUNPjpjjSgVFPH5qZGCtDdSgdCstDa3wbEgwv+XMM1mYfYvH6yturLNdAk/nIRjid8O4f9ouGzv6rtkfO6f/8XlrfeHhMKdxp2E6wxQHCNUiuSBfOK+VnBSAoBNntsToYld2aS2N3LDX4+jaoIb/dQDwqIZsli6kiY+tHpvip4UkCUSzWPd2QyQxcmCbJPRu8ERAHwGPUkQvWFEOWKwJjC+nIJxpD/qtqUyrWIgPkXsnHmMdRAv8AD5UyLUb9/gI5Rw07PdybgSw6vnaFP9VG7Q4n2egLTC2xVqsG38MABzv9dc/8/UaJ0P2kvjjMuCu8lKgtbfHf61Om1nAUdfeFYRzXZj3wlJMpgjqf2vaEvhJ8mb3Z1j0jXicLpI/AfD6wf2qxXxUln1lD+Tlz6Cms5Bv0xd1hIZf7lIWfvb5b80/RQg7tbCJ49aS3YyPOh2pP+a9KTSmWLc4UOTG2kFfLPsSflajl3HzXe80yKoGiTnezrL8rJZ+YYgqa8JI0j3uKw/vM0dnC/qoGvviYtfvKgp29rFQ+ek4Clhc/tGntEHtWqKKnXqhCbymReTtqyvxYlaIEqZo5tU2XDmSi1DT0v+qGNVBd+HKIStTPh2XpL2z8H7XTQCThba4UzqpheFb4qGoZ/F37yIKK3IFVCaqkMC0M/qr1FJhvvVFWq5/V1aGMqYqgtzT5aKKfur5U1N9afN1lJhGic3mV4T8rtL+1/6Vk+/J2GPI2lwPvXfXm/X+Ny31B1NOMAgUKKcn1X9WwDMfGVjNX+0ed1xMINDHcHR6PrwY+gmwra9KkAhLdCqRSGH8r9FzNLa6nbMAFUG+cYXjtsKdNa98kRBM9EX9RRsQdejp81k0G+ge4+K86+Zu+rELGj5klyNzuPtLmE/ANXBm5SgaV//CwCyoNW3b+6gNLfa2p9TS/axblSMqT3cWcj1xjOpsUB+tRC6cc4aKN72Vr0ZBj6r9tlsMUmA1LodjB2oWlK7XJnxgRSFXf6Qm5DrkeQrbyUgzo77WMhUlsoaogn0Stm/Q+nqVY8s0alXuR61x49DbWBS135fTfazQgLnJoJlSMw5dKIzsk6rJ+OD4DYkdfxNVWB4AKYoWn2Oey/lpxAcbvlKb0SXAllio7FXlsLwF2CM0sTTFo0Ssy7t2HfJvb801O9AkEE6oyWvb5Tq5UW5SPB79ES7teEH89wkPSCTLuaMkFNWpzECTrUAN90vdztOPHOVfw+o/VIeMrunMqZ6eVCMNQ/UYbEB/JxlXZ3jEjPdhAfSewCUZ+Ip9nnuJXUZRHBdWzNbyQbDlPZZarvU6SSApsl6NAVWPpdyZtlmetFYZlaTOhOIrU2FwfcLareYBC0DrSNlEwFKYxPZqboy2tBd1JPCJmwxnzG/p74uAq1VPVPZm0fUkHOXlacS0ZFnPNyogLvGt5KN+wejk6vrT+qFuduQWsOuprF3yw4zvsrzo6BWCQAW34Jyl2yev1ekfnPGpBIIuiK0Db8+4naZEkuV+7/GKxF/Y5hxfj6gTHKojOT/5yJDnJmJLZxhmvvnE2ZEJ1ZXi7z1ed+KanAZZfHTqwUq1vIdGdvJFMsrWA3RfOorqSw3KQs33ymy/4M1xBi7xjblTN0+bw/W1Isft5tCTsDXt0sAm7n/+qSL4sg99yZIDxDL/76QXbQ/Gy7ToOeukA2F8ikCk/vHwQUnChkw+C7CmSaG+nRqKINRJucgkXeGS+zsWYhshv8JyfU8AYcVHp9KKx7RunNvqEabVRpKGlKbFfZ9oaxiEuk3o6mdSiG6gpNkPr4ChHV1gDzwMZ+g/7dI/Ka19eXWZ+bDSnD5e97/U1XHfJshTrEBJCdYHSq2btzF5BhyeTcv/kB+ahNCz5UKMQDWRZDpLrer0T1s7SG9G937pKk/7ynndD5pdPvgXbovs07sAfblbVvhIDwzd0gQ0TR/rE56JbblVXqSZUvaNeiraqrFcaHNjpsEvD2DMffmSVqVi8T5yIzRTcEWwhzswOA/2xIEkiXfHDtabJilvOF11bVZu8igave/pLfE7S25dXx9crPIZsNJzWx2NGCB+AnaArTo3+t++ZRXlH8MqOVRbIe4/y9q8eRWUOwMRs/bZLb7b4L3ZI2d8H0vdmlRdeDzj97ZZD4e0nlAqle/PqwBbCWG/NDoZhevNgHMejqgnpfA3c51QB7SdaV8cBLn05DvCBjM+UVLUsRP1q+DPIircJ1IVpXqpLL3/B37MN8NkbQ7P8iXaT+fBD6BNAn316A8VSKsi6RqpFpo1+i7hPDQB642fO8fTxpCvfaIiE93mwQeHE9PSd5CUpyVKQXP1HFu9mDqpw7UbtxbSgwW3Qvqih4OGxCujUO3uRe/GOCZiEVk7pu+cmqdn6MM090Stuz5PFs9QYIWH3yY/oi6e1e0xTUEAp5jiK4A+gS0hAM70qtlgMZJGJqSN8oRRKLvSt+4lVTnBuxtu8PL6pRTqgBzX0GhTxSeoDcHwCvAosUN4EaY8PKy5oP+ixKuKwTjFeSMGZSy1HsucqWJNYS07WnT5Pd2wCRBk8C15GERIPac78ex2/Lfw7SdmjNsRc6ZtZluFIli3DYDms8hZK5f2cXt1O8otveXXEWUdq0g/6HLFEAhlIEgt6NQPZTt98Yb229R4WyrgEAR8cphTMyo7wZMTRGBuJh57WRyTzyvWwGKWQKrA9T1U+egH32CgHOK31UplZo9NvmyrG24IszmE/9n8jRnZvmA7r3HVB32QIlo3hUGChbyPNgAXIzl6QH2btY3bITzcHmpuOQSpHf+WHy3Zt6HGpolI/luNpP3x3QGBCGa+CmV9ilWQir4iRcnKTfXSQF4IFhE/rxX5zdRuk56QHWOdwg9oqtGUYuvxaVifTOnSCFioHKnrjYvWvNTP1BpDCQTEPORm7sZ6Ff6OAT0V8QR68c9DVN1SQZcrekaBpuAnp3FaWQxsezFkXdtuJuhIeG+8xlMigJtnH6bl+PfQiSW184ITd3e+56DeHKKXH0yuWRph4R1t8ZIoo9zlKmWgbfa3meCv1sBXTfO5PRjmeooeNbZ+UPtImoVckkHT3/p9IL1cCuwiOWvZyzjvSPc3vwAuzyuMOBe+AgvaRc0opUVLT4lRgHI3FaqWWO8UDu5TZRUGFPL7xr4ZJtgl9op12xCp9l00+DadAVPTx0lSofxCGcb7aPAOmaluoXEbhv5G9zM43sr+OS88Yt+lJc0V16nCGdy4+n5r9Ia1faRZYwLJ9KSEkihACUDwfW6VsT07USeu6PsXH5Mqp8uYdNZRjdXK10X3kFUWNbOmXLbDbI6T59GEoIPrgI7fgI6mGmhrUlpoo3dnXdKT42Ft8EFQVB5Ae6nV2zmYMFoywEnn9sfukzq39+cGGCp1PKUfyLb66AxVO8ysqdDYJ4VsuKlEdvyp/WHhPog04iKkUal21ttmjA4qzdygJWSJXmWoFB9CvAIhH6bbUGNp6zl4qZAHxYRvWvZ6qmvLc1g9dgU6tciUESRk0j45cHeKZ9fUHN7z7Z0mQ3bePF6KyMAY1z3zQszzPs6X7F3lfeQrxB9djERJJWaGchaibO8gBOQT3jKQ1MmYzhTsQPqBtsBszNpyeFx6VHVrXCzvbdzwFtqq9JsiZ1aNAorZJmfAcNMeXatIJgUbU5ScPQFU3fhzxX7ut2vom9EUFchEWCmWrRo/+lxDffZIZyZ6SgDVKLFPJlCZoEdy4UK0iU1efTCxySjPUKk4yI4aZ8sMjWfNU1FzsP2rjcEXdWIza18KUNv0GD0tu7pRzaFAlVCzfY+8NQFmShep/cRB10MLnbhPlnZfnx6ByveugTK+pSQGxzIy/V+tGBHlfgyUEVZpBCGa/JEvPf7yZj8HvglqLyqiqE6zVlfK+2aF0+6lnKDXHOk2BMlEDjBo2ZXj7WuC6I8lSR0ufD2vjyYXwLz5SpG9Lg+2wEoptgbU7dwBy9rWjWfMFvYqpTu+H0NGPuRkVBD6WB1rfTCjlpqrlotiI5Spg3RC151U0/HzYBa++yjY2nb4SwOsFxF52uGZWCvRDOBKHi/Y1HfvAUv/OJn6RTfzsWHPYyaVFYNQLjp+Lq1Gv3Mve3AehxQE+wzczsQ8LLguz2acrUNzSJXxGUzxOajCkFQd/4Y6nA1NPGra2t9ukrfjiwvEOV9txumY0Ds8X1b/DqzLdWC2oPxQHR+Ps7BQchbGUCxASNv0MWWW1GZUQPViCxueponvthrha6Nn+6wmpm8uciq9yNea4WHt6z1KKg6XR7NbT9fwJ1w37bxasAPm7qIO0ECpxdV/dyZkF/5PnKTIDcS+gDRzxsyufpylQbFYEP88DmQDoGqeDXXZO1UIW/mS8+3O3rDsZX36mvP7SRRjnWxcJo8/pZLXRUW0la9bf+0k5gmX70mpC0gLrMlVGegsu/K0AzdBaCI4uRJR6Ys5tQGz07mCEkvm/K/gMob3PVWtqvAdcNYW/1Rb+W22R5el8nsVQ/ailvO9TMF/VgrvtUJ26/7UfOLAkU3S0zWtB6h1pZv+pLJAt0E+MT+3pR2h1yS65XfDnXs5X2wEtyijqyH0pKiv/z72eSI3OFQj8DzwQXh9c/BS/ovl86qru1C5ycUg4TZ2/18z3HdjiOLRed3X/9i6sUMrPkJvRhF4J/lyYRcUbKadUJJE61UZkUGXJ9lJ9GuZajZbLp3dDDVQ7kgXERjlUPyXnoE+t5vhHigs1DmFNNZN6Ni5O7HpjjdfI4oeF+ds2mt1pc/iEX0uWeUJlHILAWRSo79U8L+nUJBDowUPS1yO+fu/fRqCl0LgyDePrtTGzJJ7u2DYJfH6qIzPL4kY45j5W73YTRCZRbqYSfXkAwghC3CwcXCrGnQQcucIfqsCmlVJNRNscgv24AjCAol4b8a+RH4WA0SthT+cgVJTRiIPKn8r1xVO8+Ipent36jhl66foyQ3odnXPSq9NP0l36tSM9NL/VFumTeMmUyyIQl1W+qHXcbYyJT7xpU7xAYXvUgdpzSxG8gMAK11d9CG89GOXFVFaKIiI/jn3p4XFYdqy6aR/Fk9TPxyI74SOsthn21hX+jmfbr3iGiJxVqVyUhnoh2rrcjdEZVG4gqyLp+S5CvdToI1vf3rzUOPPBnpLOcUTTzqpXYG9LDrAHh6Jq1hHdOGewPzLnb6sz5q8RZXuTFTiQ1mFhqnbXl6nmmCc17DRqGO/p1k/ufLvlToI5B7zkpe3eu/lOg/LhlsoyAP6sdKYe5BjRIB/C0jBcziSFNY2+Ya1fe/+xe0ptuWpKnq0r3s7bS62f5W7kyhVs9T6JVZGL06IIKrKPjSyD/aVxaLVPi61CeiL4zImsmHR4x9AGzIzh0xk1Rzgli4o7aOlw1ea837Wu3MZKoy7o2Y7Kz+czAIpHFm009WwGNlvRJDrnQcOeCXPzLEveOIalP9w+K6F9pJ8Q7zynOjRvWR7yr3rLhH2wO3owpnBO90MBKsgtxu6tyyG3k4xJgc/N+UIQM1rE+G8vuX5pTqxXYxtYoqRJer2JKpb4XPPEatsXvEa+Pp8P/9zOhqRSqjuTg5iKcLYWhScFB0GxvhWTpG3s7lXQ10FZ768d9ew+MBUmcpXsYZAEigFA/V3eNSUZEitOTm3bBXG0wgKRqs4G1KG6LeFpLuFvFSnMV3SfAWsoUWXfH2PWZmUuQM9v2MBpW21Wv3czG9bXk9h1zWNk97UmQbA3a2uabImiCK9n5VBKpEuqysmlhZkUd1SoymbEwwErrYGy92/SK4N0eFFsi+8FQN3f8Zgr1DvTg9IsI30MrqBh5SxpsHuH/3buNcgv+zGmw4TmzmNvep0jwmiaR6vpsApLtLVNHMdgWZ1f9wkwJ3Tvte4VTLcXhh5qt60jjLTO//ctFOKXEnqDtF92TNLfOtIMZshQtlaJ3nleIiudDJmhjWfVLv7X2woC5WanN1/ck3TQE8F5gPW36aOHE0pt0l55NfUbGWg3MBIXxl5pXidmCcsEhEC81o+/vOizHjfrnMmoWAZdYzIEaXzdqYxuwXx49B6qDNSGXOXA8wzZTL68uUnkycxfeLbMN40s62Uom/EcnlWhgb2CglryzOSJlXInT9YBSH9zugIwIlnnWM25LoXZ9N6+KGIwggcbPoTyMWKUzK3xPIvlFKm7XryrMHo7KY9li3cINwypnF/x8DkrMVP5h+BOZjYvDQlKNGBsyhVvSR0JaRDymXFY0LSFZ184e8ek8kGo0k6o84Jbpn4/bnNbsFM4ey+nC8HQK9A6rs/EB8qEQneoSTPDOX5rxvMd13zB4dm+Nf0so/Hhg161gqV0gzEYSV4XaaLbu8v3z1ilYlFbTZDqZ+zA2Pdma5FGb9KZe5MbcZ9vq9bPtC71t1ZlzkngB0fQtKmDFwfU/0UOEKvo9JqoaSNRipQFXrUXsu23eM36NaGhIf8GtH4KvmpUvjZtkk3UmmsZeTeloUZfXJOV83SzGmMIesqnAz0+XZsEzjLvVbN67eeaSLAh54LmA+8YeE7JyHJ/rdvMEgNwdamgwbZt71h9oXvFqz5BGYpOvrpnHpIZ+Edv6z9kCeG/P+zgd3QB7Sa76OH3CWxE88+djqzuCJtx/8wtKXg9RQNPTsIpeedxBdaNdSuvlKOfVHDriFGotwPKMJNXr32vcy5qybkVYOEyqGS8mtKalxnZ70jieD6N0TFoEMc6D4E9QInPcydjMYFUIikmP/nw6bt7VmG80e4ou3wdqBqkkfnreGpzDIpeRIrI5kJ+EhkIaNXCpF94R/3SED/RAgoZ39tbMhPS+er/O65BaRuFc8xDZZnvTY7iRwYnGjl9fUjka9df9KuOERSngDB8XFfHYJ6/q3Xo8U1BoI1QFad4Z0LynUqrVoHIxbMFmqRp+jZZifl6vUQhsDc1AJ6pi/wDd7DcrrMUV1vYx4LFb6dk6myLu4nKEV8AX3mzjlHaJxNKFZWchjNvvjNphfZwBjLbvDG7gvMENSrflfM8c1Wq9Srsd09RYMFFvVASAQARGV4oOkLiZANdzudeVKEDmvbhDBVZWcX1mwA4Pj+6NzJh+umlOJpOmhRiLe4LEsXzpuK1AorSfu0PCaVZOOvloH3CgnmILLqyB+6Qi6oZZrSGBk/bmhWpjX1bF2wf93kh1mP348e8kPiqjl7WqPJknmoqmpyzyPvX4sf1N77fxd0aOZhT7zi4bV2LgZdvHYcD/MdwObtQv0r0JMUcYX3Pbv7ndabmHS2aaVUOPMSk4HDhSc3KcfzXe3sUyeQih+udvlNiNZriyTOMZg2aaoKTpNkkGI/xtKEa9mnM2wWNqv0ZLbBCeNRDi0sW5Dr0c3uUOnfGQr1y9nOzDfM44dkxP7jye/Vu/+wv6X0hCCQYaoF8L6Cw9IZ8W5P5qdTjiI0JxBqaQBNR9FLhNgYpedZ3W+0BAY0q2z5P8xcu0bmMla++Sd9ARKz4uEaxIjLTfpL8KPYnyKlPIXEHPRZtlvNoztu6y1j2m58lpNZuQFwv2jGrMeH3+2jslk8XnRN7PH+twX4b+3W9nht2XFQOlXeQEKXwhtkPIlHB7mDzv/EA9HpRLBp3pIKm74gdvN9kJecLMGtXcJrzYf6PGon8F6KpVPPxmIGD0u33PeGOJBdw12SaplHrbmdjKRz4s/qUkQU4XaZPN7WZmJWjsq+1W+XBwala2Zzk17AT2j/3V+mXFXMId+eQGjShxHGefXkxBgaQ7hcaWBIob2rF0lYcmvxvQHTRFxmMSgnXw+ZF4Qlpp9d150/+BXZfKR6b3DA/bPjVAGYSjy6E4apzECT1rrBWsW5s42nVY6r8xKyHBwFX3p1VboIgqDeUeVXOz1VrnY2YyaeZ1RxSELDEmyg/OJt5CuUp80PwMtK4UiCc9FN/nMjEn/FEuHkF/1cdxWTyoPYidYRSg4pZll5mrVTW4L4OckFIBvexSxzDzcRjhGr/V71EYD3Q0jdxWMb1ZoYHnDRnOqi29wKtjGNwPSpEzWZhx19aAs+QrDQkVo8LNxZ425gyJq7ys2IhYsG73lpLVVzmL1ZP3aw+vVm96xtmekeQv1fnqa/9cxTmR2j99/WvkTmUGwvLF3Y1YDsDWdjq36O0mWzPC1XE4/s9SP9j5dzgbjIF3nGX/VVzY5Y+00MvJZgGxX0cy+Zew+fnu7UKqKr5j618egVsJgrx3WD/X/cZknzoEXLiEPE1ml/Y7I++pK/YvLKtTWSrf824+HC22vhRdbKy4YlzKihtt/R6UUbaP8/3AfLcyffyT71CDrXCN1ar1S2hxG/apFTDX+8VA/jnnqvmbjV4u9peGtf7dOPn/9wfeQqEJiUytn7Bvz/qOsBt9FAspSpDN6UYBcu/GKrzg8jXSW0McU7nK5fuzhS6+R3AVj/UHolfOdPyFVsxySAiEyGcxINajYfdT9PrVN3C9Yz36w4JNNoCrkX8j52lJPvodVep1fKVFqWgcw8PXmmoVH3kpTyl06kpid7b6JLSv+dcLZ5fWJnnof/8egOR/rkWizRqlw0DcTcGPG7TvRt8QWx0Sek94yk8f94nhZrCh5ybqKWOYtXK5K994zryoWSJiVgq5YjhCDesKnDUBMZtaOttzJi/3jxFtmDNI9d87kgtvSIgdG2goqpU3eG17JROqctVQaI/S47WPy3q671kPpvYSmEd0v+l67u2JsWOZl8JW8Bl4b33d3goXOGhnv6w62vN39M9R2tJF5qWuoC9MyMyIyNlDshC5s0ypU6oJX/Y6IdMUucau9E1ijkgBAhS4X+pmECf9lvRrVS6zjyuFq7Y+JT716OSmhMU8UBLnXx/KyExi2XSdvOhgUlmrFRbyeP+mTlVdNll+Q/uAI2NKhcmFW8OKEeH+/QJn+vyc8rphEYaldseW+0f46bxxI3UygToBVBbpLqug6GAbG6AwVker2wZc766647EeRHscos/dv8xgtYUJHUSw5S/TkmmHBIvxLxXTdpbq/Xr4SagyMEP3qyRWrF38djHiS8DOUWU+OOkeTwUOAFOtKDI8QZdFf45NssRGFPf3bQOefCbM4GGcnkMhU+jM4vjVLXQjHC4VT42pxRwzaHJL/OrgaWZG0VnoNlIoK8Sq29+EkAyRDDTjtGRB5cmBJxYhCg+pgjbb/IJcCz32pHZmxva7UD2jOCZaoGV0z8Ofr38eNJSBV5QzD4ApWlHkA7t3WVsD2rfJC63uj9PAWABc+A0iMQgaEjALaDbOnaz1huAzhRV54PytTUlwzESy7TjHOH9DnYMgOb9IX/zpHY0VS5BXmFKsn2AhK1NKwfKB/jVlUtVLRtzc806ShaRmT6L+rSoxi65spcTdb/vMQ5VYUhLBX9dF4LWBrzpPs839vwVq3heKVN1Hf6vns/QEZRUjJzY2V521icCwmRii98zt6eq4COcqqpcC8dsD5dr8lG5r2zKTOy3cswvW1jEcqIvBAgvAqrLj0eS7x7cTjvP4PFo5VjcQ+8BqjQxNbYF0X4pD9mKBU+NgGKqVIs1P346PPCJ82UUkQUQBimZ6w/HeAPGuRef6sjMeuNDrvpsg4GzK+UwZONBPGNdfhcH1V5bxkKgpdc8C3bvuPJZ/i96ZMwhd5IxnyP13NE2eb/9WCHhNcmFJfZhtaiKtX1QETKjYr5ulxhkc5R3q3ImwdhvRPPA8viDzpQcr4q7tqCLC7F+fTwiA/GLx+XmDpybfU0Yq1S+8ypGL2r+hQZYS4DvoHJooHShqCqRpClBxPOMIsFrh2HeXAGDIMBgA/3JMpIEwyyhLnlCYz/u1HsTI0lTO/64oAaZcAR1VTDe40xzSWfDDaRiNreAyuKXHyRJM5EaBGZ5mU9AKnNsy2FT/VI7FFy3YF/ODdDoGIeRC/lYq+ua1Sa8hCfRQ4MJOlMt92JejH8I/bd2sLs3i8Yjl0Ko3lxfqlYkBNJSs8ktgd7T1pfH21L3dQGRHNoh8Q+6Mx4xpijx6TET/L3GO9w/IwJrIyHTQo0J1JeX8FnTe3Xr0HHQjQlTM76fPlTR74r9CYVKjiHa/9xRgR/cWcey4XDoNGognLzXzgD9t8F2gjwu1/OLVncTBYAV3dAgjz7KEfe2S2rbRwq5QSo8qzEeRWDNxrPl6lPH+Zejv/XS0ZVLBZOC8ZcWPwCnn3jTnPM4+rkz6yg+17GxPuinB13ohOGJz3PYE+hNtb+YYcZXDsM+OOedWMgzb6FdqZme8CGRWYofXQ2lPz7qs1HmRnLEqKPCoOWb09h41sT/6UEJLf/81JPM35c2VQpkU9aXg3TbzYNaxW+nZ676qMx0ODpieDtxLJejiTetq/yGnZPYYI9C2hxE69CBVfiJfT1rD+lwJgEVO5vBsHGcj9UoXPTTgHJbnH5Hf1NqOQfz/u9JyRpoxg7Doe/MAj9fhGGWu/OyYK7BEvpB1FsjQ6AcTXP7uQ8oUa9LZzF2sioN+WinUWHoZ7w7C0Y8658uFkcHLqqMgJeCCqRRQK6wBGjBswtCgEpos/lPrm1NdeqIhTfjO2RIT1q2Od7THQuSuE3pFhHjppSaS8Up+7Z2HIukh3SEvmElQ34UyhrUV2vjczxVue7nOHPzcd+ux8min+qJFXJ1USRmrQhFweCw0KH4XGTKNM+GSFSuyeKuS8o6qCZQGfQfhfBPx4/lGOtY+VaDteEdOrI8Vc06vwNUxcEltt9rbGsWyl4vL5vQ93mJoyhk75AOLBsK2d7m9UWOE0SIElh8TQjAFx8ns0xijqFVVMZd+OqQAuano0z/+BPxRuZcvlAHM2uaJqtpUK4sARhcOQWCPEtGacWkL7plqCzC60n4/9iJLqFPXKGix6Jy2IcR09d0tWTTWbCpg7qKdKqGQQVbNgSBZ/4IdfkfG2ngNDzy8Dy83DxLVi6VC0LvPzLsgu8Hse1+8fSYAA0G6cj02Dxp7B2ZKK6ZGz5rXxHwPyykSiKH5L+QyBR4/TGJ62Maun1RTd8ar0gbIf9Msl4yA4fbYtJ20M1ykCGqpWpX3+Z8Dr/YSE//uOYYODU7ss/VmsPT2rCh6cay5EuWcTKrWxg3UvQM9ZWSp1N2p3E3zzvt/Y8ZGJFkM8bnmPkQHpeDBWTULF+hPeWyMYJa3qvNfO6TuSqpZY+bF0OrQlVC3FaXMwKEr9LCTNQqcP+pMYf/IGrXjcdeFp0aoViqTPoaLsWqPAnNjj4hhZ7/eHNbuiZdumwH4PAp0PsNUnR4J2n3lUVP1MaY+xipTi10vi+8VOjcnXp/vlJtWrh6Jve3cjbvAuY0JPBNapt0VNHmY7R/atM32Nz0cL+gbGOOTG3wzAd19LzMmU1tLGr17El+aU1tSr0Dp2BC8x8PjirzDsVfmFdJ2Bx+535cfWGtJcmjo9iZzBJA6smLknK0zeQrd977ZpWZeiF9h3y7jwrocX0nI2SK8TGVVe3WEU4pbgYY676DdKgCZYvDgMInzTWyhr1Y67epDF9aEpb2gVfU9H4Ob997BZzWOGR9DLo/fbbu4RW8lVlnSrZOJCRLNrFh8kGql/ILowN3OvkO9tYhc2NrnMGHzyq2UvgT3jCfXRJwgl+B2G6JIZPQPxUKrufsKpazd+0HBDxp8YiETK/SMOT5YOjo2EVRao1GgrFeZazX5HPKcEdpqUWaQ+WfPw6OE43cGcDJNoGWLBF9xPAH1K+1PTmbLi5GyFbDV7ZZ/yiJ6HN+7lMH4WfIzD1fJI9G8uOurN+F/q4Tf7YWF9rmRl5wp7uZzpX4MA0OTymnuNm8ZlIlvroxcY2xRtShxfi5c7o0inyopQUncp5A/MOGmMOx1C5XRwcAO6LErMjIP2nIIsdb792i1ZRxprtHi85Rf2xu0kzL4uhWk8pewpd+7HIxsJ8OkcU5g1/9VKnK4vywn7WGqn0mV3/oANjo/jE3sqHRWKea8lEJf7kYs3coBBMnJlaxLjUb7PHnpIUn6c+RS5Q64EdLckEG8pI38292xVSYZSWyp3JXXwptXdX/7alMn1LrSKU9g9id6frrTyefO2DYY3sfI5C5Kf4mBJkqM9Yf0wpl20JMZWNjZUvcgxH+rpjcWYADtolgrgU3KHHk/nR9l88ndrNbUZfTOY5K8cP9rcQQMk5ocqYUk8UodC3/j1/btMzLlppFuvnpp4X+mlPRgUe3zdTUO/PJ9wM8l0ucD/oPV3AN6yABT+ALvN+MO3dkKhnS+/23kFzTOOibfc5cvQt++0FLdor18wXhj8pF2MiWdsGK4x5GkBs/g75J8Agt44F9zJIJbCyODP48NrO2iS3TEun573mSQ5Ya9nSUN23pp2W5/AEkFF9ME0k/Egqm09bSvCMiHyMB7icLNKq++nmv0XI+I2nm9MqYe8KtjDORn08oHVUO8LlzFNROIV5jy7MnJbgf0Cj0zQ5o3H6rOhC8xViQeCM4LecYLAplZ0z42Qc50uSgXmCjG+3M6lhZekR/kvsY5g5Sq3XVmj4jebAa2npqfdxuXt7vR+k/1pk5ODneVuQOxNj6ou9/g55wZfjNb99So2taPxip1sWZRphLaQSf7CfOOfJw3q5+qrVlSQ7lzPqwD31DaRflgS9+A9/B3w1k4Min1Hjg3liqtu8cmXL1dNRc/VEAwZAbO1K5MyvapykgP97w4FeQSfD7hgAN6Kg7rnrdPwS2feuBLYxnYDKPezEznYuVNiGjCnVWJXHWCMDbORmX+6485Hp9XVgkc3xFmsJb6hSsvFYRWSvGb1VXbX93nbuj7VPgWj9wJPKRGev12taDzN9dirUJgBCim4Vh/on9yHrTkeh9c39woRlLuuHqLiHPYtx1ULRfC2ivVpUXY4vi42KSFkXB/rERQxJh5k68LWBF/MbfgVWBnbdWvUsXqsd1Tsy6N6l6MZ/23rWj+qS+EznserWhQKTlTd/zLPOCo1lqgGSz4/Mhvh1grS9X4/4XibeMLxTNcYQiboiP1iDkXY/rf3mIPSP5w76fp7zfPFr35W4zQ5FohJQn1/44m0B22ubpaskLXssL6NSkZ0BTP9NZNqcse2lwKvV+iaaQakXD01mpyjK1wve/BhYljuogiXb1FtGvn+yic/TvGyn4Jr/PdrtLjPqC62V7UtJglN2JaPviR2TaQfFMLgmtRIPG4HegIFGVadXzwbPAZeIMy80PrRXvOhjufyBjua/wuu95UFDJOSA2jKICtxdZpx7dc15RFaPUM+kQn7NSMeBd20s+8s1P/6fN/Zf/3Ofp9fQiOfG8zLSHAgRcoF1tZ8vikpDQRz7KqXGCBzYD+gRKOw8UH9fvJtSnR6gAhYbK8OuyJnDXFCFVTUkQJM8r2HUF5hjaaiF6dbBGCESUeoBLgfY/pi004t+sdS2j7zB+nx7pQH5gglrrr6v4Wnw7IH1PykvA4E+ZUVvzocIIrTYHobX6kO5oD83KtSEJVWxaKTT7Mg+2gIwlD/C2XP3bdbGy29ZjCU2NckN2orlhbEahgpnLxpDz3uRD7vaUYj7n8z2rzskZrdqMnXSDxh6SebE1cDW9BsCHUYGCkWoNkhvutO9Xz09TIWRkpRwP3LHsKYBUT4h6AQgdaBLSsJ8HBiKJypKtYSmZnEA/4r5/OhUDKdG5c4mpvl5b5XDMKXbX4yZ3EmeW2vPnoxuciVAPqsBMqdVqW7TaUE0mNP01RCKJDelOL8k8/tWvYRmuwhJd0EOmJewaCcO+hj/mhAchxaundAbQS+9e7744MxfE5jwHb85klvUjdPebH9UPM6uoxYxSn6bBssYMtz5mtUNhqzc9KHr6dyqvsMmB7EmA5PZmvN8lRRuwNL0/OLEzz+CFwWl4/90bioZxyj2nK+VP6AZ6d8artZM38a0J2dke78gEHoia9TUJBhn91dumjvLkosVfXe8dz6mXXO9/qUgNJ6liYnGUBPVStSx6nd2DlSNiaVGnBIYKIibGlM5UXNlmhrlaya3UmqKWh09GwhCs/WUFn8miE/8VBFRVoFkZEzAtbkefYZc113D9rawzhy/2Lng07f42PTsMKPi93PPrOQSKpHsZ5QELt59Qasadkxb77QzFIBBI8zgxtNvcUqS43zsjYFqdYTPL9zZh8tFLeaHkXlinJ7Pvz7ye0lyAmhJBlUn3+OGoswFvcZCwQx/4D6bK4d2sQlhBMM0grneuJxgdmID/LV2a6lCXHAP7mQgwXX5BJPX4ijNS2/VupChLgl2tdXumDWZLOlf1qWzs6NeFCvn6YV+96NbHod8Pt5dxAADGw3/t+24aDIrV/fVW4O8otqfepOnCEmaH/Pz5VWxLwuzSUc81x2Lxl5nMGwsfx/+ppH6hIA6rOdmRiMlUioeKoTakqqm+aCoDdf5emAFwN6ELGc5Xv9zv2GQgdUAxRY9JiOhjMNdwtpg9GTuDBud+Zwa3bJ/5fpGf7zNX/qrn1RuUn+d+/aropF7BCzCAAsYb9r4YBlFWSD3DiNR1qzl8fcCD0gsFUxQFPD7oIghpRLwOMAdEHpB2YilslX6LhHItpXdeQzOfT81AsqN1p9BE+r0r+33C5w0mo8BLIakkUMSOqXjj03ieAoSAWu5tmZK/PNQhvfmk79RZKTbBKSE9HMumE4w9g72ZYf3ishhVyhxVuVavyMNWNNLtHwc8O4xDfuHfvn5Mw9D0bTdFYYwMPLjA02BgUoiOXk3a+YVD8ndiEhmAD/XAc9vvVqGbS4Yfsp6Ct/tOXkRObKT1r26KfQpjyA5Kjumcur35qEAPf3e5bQSSGiqdxBsXtK6rq9X8kj7RTCcsp1/2uMaVrpIzHo/hgqz4MQ75rhyeRjAvITdd6YtpAif7jpy8xHoy3IwYzdFXhZUgboi3qwGFOevcd9a1bCbrQtdoEnVQ32dNb4BO3yB3ufSL+ZOTWFv/3ldzG/qOYcn0PqUGd+xIcnRteS8h3E9w6nZ7QBFJHLUkTGvUyUAlLWevKYv35w0SZCCuCMiE2AZl9B+J9ktdASKza7gtrYkYefxSuEa4qf+aVN1L9PHAsHE+2gU4ltEEWgImR+pFjs5hnRuRBmN5kcQxlmwbeP1i1HWqPOrcS9rMK0CWP1yDMYgZ+Hf8jEQc9/Fl2jz/pWr96psN3e25fV0PKFe/LdGWVol8tLTVbx8TVvpP/YM+Sleko88Azk1Qfecc7hsr6WA8ZfaAMuWUXXRywtDU9UBQws2fEd2Zv65gE2h6Dmp4EuT97rWrRrU8YmbGIrore9N/KtGBThzkfs6R7r8jM8udYavT5zREl236IAudNswy9Ehdpu2rnGtM4RzmFEp1bZqYlTG/PPP1D34HbpJwuPcZtqXK3c6LO7G+DcVUkRy+weDMEWMFET7btq0jRP7tMsAiTG4V1OXa3DOT2Zoy8DwyGossiymM58+ntKN52k5Skhqnd+w/e5qAywLthlg1UiBKqHxiL71yAhdjjs6393az58uI9kz2vOuP7u5XgW+CGSFt7O50ZvC15EjxQjsze+og3u73g3v0OMfeZ4KaFkE+f24JA8zSHPmVtHpZzDpKkVOr0OeXIZDU6Aj1shIIkuXGX065Kv1hgouQE2tzmqPOrc4Rj7/cLXqIq2yuW41+RMwR+/eOK/Bn9XarPEfmbqirn57CpWb8erPHXzvFaujLjg8ncjmzST4VfjPJv12SeylkNXAKRVS0tvt3AYuw7hTy//juxuEYRtTIXCpmyhXR/Z9eAi9N/nJuWvyYDumOUvS3bqOseunORCdolbDcuofU18atNMSBxuk/O8fxqWF4I7d6eUitHoztM/tTZ4NLOtgppTLV1X0NH01urP+cy6CjBqKBe0c5go6tSg7+f2wPBLGBYW0DbCFrzfZxFJX+5/uXqxZywHzHV4Fwktrjzz+Bj9RzhG4gxUoZKLXihjaM3J/Vi++UR1QRU5aJTv0fO6OeJINcMoNrmU3faOZddQ8RqkBdstmFIW3ypWKtv/UkPUQTxoHtrresEsVVks2nWi/VWtpTcDCt27DtQM4kTHXWcH/dDyFxQCfvEu5rLqaZ6sjnMC7OmfKR4CeJA97Ms62+bb9/32uD0UFFwE2JgtbUBt8ony2CRVgFBPN75xpKOptbao3q/9iAN/xwFLXJXw5XrjdvFZVNDrk2bSMyDMwl+u/tgDImJlJjBRwh37jdwWasUDk7G7lGT+BqvKMn0D8GWyj919/ageiYKNu3YkMtN44LPmDaLqUsRPamo3XwLGTkBjeF6q/7EJWWPBsItYyAFeZuU3jm4I6tiC5fg6uPADALqfk1K+5ORVMDjuPU/lWWbmsW8jdDTG3J1l7RxHD6q6ZZiyqE9xZCqPY+lp3EVWbmaqFR7a2xwFYZGk/GRih4qyPC9cp5cR3nWQR/GZgg7F/O9LsWpREONZE4vt2J+fVi9+27SaGNf7E6TOqOC0o+vxZIffUb0/R4XPKHOJuMT7CWmJc3Lu1ig4cTNQAH1CNWGpOBFK6xErj2TChXHuSAxO/J8IC/Bz7Cz3Rsfb+rtyCWhCwXZUDBNuHNVW+XHLGYzvvmQvFJHt6s5zkCgzVQO/OF8vEljnaCcNbzIdTI5kCb+Nm6CVL2RgvbODK+wvsZUfKfZ7Rx+UkUfHRTGOWDvgSw5On65kpkCsNXXZfpPz6l+VbkOTWnPzvSbvh68/o+JIwkTY9HY52tw93hYdp2dr3sJ686m2xHkXofdle19cdP4Yj37JYBFWXtGKRpExopLzrp2ciofn8rh++ddPHEcPJbH1c7IXekpz7LgZ+k1+yM7SZ3XezUTLB+lX58ERpWb4XeukKH5u4xCasW+jRO8Bx7FP1TB5Z50HGuNUL/oaLfZ5i/LeA54KTW5DActy2CzCyXCIE8hELLr1/aXEPAb2rbiqIgDWPFUN8e0/f3yNNSNPbqOwk6SEEjGLJiroa2oLsW2ABYDnY4wNgHQKHYDHZaJrYVUBLxSE+eui3uG2gokSVQVCeiEMqUrfvefZALohQUVNm+Qm0phRuh7R83gUcekT8HsuS9a2FcwKrD3+rlwp1ThQjr2lJcvx8pjU3BmvRnM2E+d6pfswYsbY4EypE6TjX3Wotpfi3IW36LjWXpvPVV3BtnunLD8zhvLDdVzvOjMrDDEFjBnQ1jcJ3qQECgMsC7SK+pwlLvRpsBgyceT98JZvFtbUEPk0KGbLHKdH0Ry0k8dVOgS+vMhLOTC/CnAbbaUAeUB7u1zKG7NsEKuf4U7cXyf65HzMF+ZTWUjwyXlqTi/nrSRzzXi8MHn6jlZwL/7tf57pZtc/YJ1JfWddGYpTRf+YhginnSnS87r7NkGZJx1t/+AEjI/DJjFlyG/JXQ7zt6QZpxzJzLLtCYOkb3HPo3+b4QBFS217n5Woun4OcABjXkad9Q6UyRJozbTcv78KkKh1+/lM7KbGkrP29Vu9kDtV4IHL4RhLVAQ+yi3lKu1Oni4z93a6PS8KFgnJQqnw8kP9jjf95bdwC6kYrVcgHL34efrlpuJT7DzdAZmDiqgdY6+LyaG/B6EakkceAj/BPnT9KRoLcaMwAYkMs4E563F6tw7Kac99oN/Frxs7wL6sMT+NVlBkmUTlG8f9BhxuRQtsZzqKoN+bl29QldwVF/ClQBNwOJg5TPXnmufMx3/PAWB9X2C+qPBqizkkPtS9Tp6TGUS3NKApLqcALtSQpF5XRlLm/49vOpZ/SPi8P7u6eN34b4+fTKyQVR9mrD0LXtjMPDTK3nnxf1mEntUEOfQyg4uTEeJ7OXNyM+Xri9B/55/hOKp6IOeLBZJwDaKO+1lnDkn6JQqytgIx/HttxghPstVbxlexUpD4X2DG19zKN4FOisH3yuI+/pIEYC2nLfSDfX4X3LsSOFo0ZzVJFbKDfoPdWhlIC3J6YrvaUrSIleu36oq4z/RTl1+fEse3+m04K55PncjYz64MQDVjLhWSqTT6BKOu/r+qLnbqOecQhFneUo8OxLPldbiXxTDxA81cfEk10sPL38iXSe8TT4G4EZ5/Ls+X5+TrTK0DUTJiqU+uBd9dWsYLkCKtfAubfphgK8DDvaJtnjK9z0P/pcu90jtgW7vclcUEu9XlUGwSag0yOzj0lEcVS/44mKiL86nrmbgF6pC1Gq+2teYPF/lkXShY/AXTcg4COQH/CfkXXed1IRn63QWI1MH5nJVUvBv/mTSl4PqDAr+5uVn5bn0/HzW3UWZNbr2Wyr/SsxoJdi9ZGtkvbznWfwiFyinDJ0pLGg5HpFfjaFN4/QdARtjrF8df6bl40IijHojJPNxOdZJ8SsRubT+60jtQjttzNQcsJPQgA5YUducEG9YgLRtCJe86JAYOxRMG2iMw9aShjmZEzsoRcq2PpQuF/MyTw5rOIbU76j7CvQLT1QV+BDqzT70+GR1FUuGMyz0eHz5HpHX088mfkUihbrus8x12UD5d+/5PdIXknPn3O8bhWiT2lsBTgex2k1hU0CVH5EMDOcApLcKoKmOn+CeE/aZOMmIqc2Wn4+TqlWe7Ag8n+ou5M4oZarl44rTtXXUc85grAuCogRp+QAf6cMeYWQPLh4Y32A67Om3EECqclSWh/hO+GOuOJLtApPVUF6FlsWV8YQKf9XvyQH+uF3n5f3mQYUha/zh0a0xr4Ki8w06IXH7LPlaFBhwAgM4i9LuqGWMt2wXRLfzA1gwHIlWzNrPZuW46LQdIWwnYtwj9tTP9ggx2gwKnDgYLPp3zTxepWBMYouKd58HNiH+AoWHINLUJWQBq3tZP6EFB6/45comkZ0SqCUlH8SrT/fkqOYNfVi+Bqo1IL7oRbhvO8JuHqjMBx+yhi+nDjSnU+aXmulzTHmlxcqmYh5BbBXooPprfKm0NmXnXs+u0vxOg/GM/1tjxPGPpmvGI1CpPitXdbF8ycorfPMFEgfkPO3O9ZXrbai7zHR47Z91rZwvc0fBvzsnynF8fcFLDrfYrd+Djzzhvx2qzzuL/WamU49kkIJa1C5RI6XcFRxAMkvVJ3QuYKh3xhHKsffyd+qVc77dIOCS45S1npNmCPzeCFd7VEuUe9dA/rjYdgebrZC+qm4hOLAUzxffCJh5bVMxHIjDd+DGpivT656GTJLMgrhbN39QhopwrnPV5ys/KYXd7mgAlsYwHIHGk32OoV0xBnzZhVAp31Y4neKZOIDDcK8xsQX92V9lFV5UTTny2IfNe+24x38H/z1fPFjcseVONZOcqzO0u6wKo7FKY6SV9zW9G9VBPk5B8NDB1FRZogM20Qiv4Qa0c0EXwwYG6zFlN2AA61b7DXn8ndCVaAliR7e9R1mXhCoM/FG6O2afm0b7mkw9gaBrNJ7lxDq4XosMp0JZTZ45UBDRs7h3ni2mIPOWk5I/9vuCmoCqvWd+H6vqB0vY22xMdsQP9u4UXmqDTbuHfbEgF0Btx94PSWO0/Vj2NTR9saJIRCWl0uQynzguy9dXVrFXaPRvzkueN85ELDTbLWokfmTxd1/hNe+WgGdPmZxlmDvY+HB828VunWA+QPhqrnPo0M+EXO8n39NfLx0duS5xr6Df92b+kcd2OrP2RSBVr/zDqvFitij6//4PT8a9Pt1frdxx0uR7Kehi39OK2hPGTiFbkZtzQYfBa+PyeLWXxWT70wB2CMeayPnMK7NN39M4/xMTtwv6bubG2Qzt8Eff201zz1J5x7cJpiPpMpVsfqD+f+axAdaEiUab2DvXvXjL7fKCJJEUfvaU8xY/CJTlXm5GLK2f+6nohHGPj+crbzai7WlpYScEIx//ec+763yEpkTeIhz72vHdtd/uJNS3/4Pvyz0aNIZSybey18bBSpucC7kmDOqqwm57WZlf7/B9WeCeePzeV13H9spHm+OJZTjga+YvF207P3X3q+1ev3sU3QwjK+joJ6R0DA6ZLsBc2Lrk3Mpwp/v5381CQAf1o3J+kulGaVGvBVURPp63BXqgLTmz5NHV+/v3yZOc3KEfZegwN2FNGe7Etn6E3FVtUj+ztjNf+yil3gEdQwfzxQxQr3HEPCf7/ztIiTJ4N4fuwSlTQnq8a5PgY0c7vytpsFrY0yt/utLlAQF3QS05CzSe4vlbLUl95HR7J86nf+rLy9dfYe+IxTjsXuAHhwIyHmmaXr/SARGb/8/zwxmb2v7dXlkySfzv/XGgFr5gL+jPiuI8vhCSse7iZPU4CMsPYk9XF/BmiOduyRRCJDiDuTeOmDJvHr2KF+WFvK/OfP/y7Ts/lUzPhsrnT5dbz7A9Ksy/4B0uph3DBs3VpI6ytYjZgpHxXybAbnL16KMY0jKwncR3EPI0W/eEeE1nzNi+KwfMmDBduebmWFJquTYOKmfj+eDmmq+BmW9UAKRan/NDS1QXGHEPirh1AJEt+zlLqj5iuP4EdijvG8ft/E36mXNqxOoT/u+LGzTowF8o6tODbm56tby/XppQRtABFqXRkt0TuAsPOwKZzQZSL4eWioXYRr/VWMVq8ETR6LjR0U0i7O+k9O6UhW675xekFqK1vT22RGh+MCH+oq9842m2AlGyNYzxXTw3oagysGpKBqo4yzfqX1qd1fwSYp9KmN8X2Eo0aMyTM8x0PMbBIDktKvFZ+Uwqh8e9vlnLbnKUkuBVQfY5toi9U6yzgD9LQTKzMssDesFYP6BosYKX2BWVKDCGGazwXGByRU6pTZXg4hGe8Myzfj6UAm9QKjt/JTZHiVBVRYLKJvdCO+lXKh6IvOrWwLx4X8Vcu+w3uuR6g0KQ7T0b5+jnxmjGlZxN40W/rtbiTOFFoPdnNU3gbh5CqCKJH4jc3UYhnqbJgfMbD5uSthIrgyYPhHczBbusb4syaSM3Td5dB18ONO0ldwde7jPhCSRZF65amlT+Fqn5O3+9a5eGgfqoK3NHA8oVF+j90BSCtME1iNE55UOSUvT+PHUioj+Ve2TCpYTxSGF2xS+AxZannNYBkYu1ZTXWCSqzi9QV3Tn5ERElbOcOMdCyjdp1aVm2NHKvyv70x0xK1vQkWy7GMsSabURiKd+TI8mLipIUSB+v+O2X6/FpwcrMHgonxP61QrAi9HjazP3T+ImzXTy3Yw1KbCVyWol/Vm3RaRRsItAUPe80AvjCMfU5U6wuIQOFlTkKyyUhocrUuWZtblbmUB4hn4H2OHTNyWIbcgFl8hUHC30WP6aaBSfy4+6rcI8poTq7y6plKip0tVyX0d9W6t7ZHouf0226nT2nL3167IOm0BvyuMYIkvmm3pr1OUzUS98+nTy/qMTk7jfLdPCIT9b0/AfBB245vBCahyf79io04TD34F008m/81v+XJdL1Lbh4Qfofe4KSrGGOqzmg+DaO6jbkf8KCPnkROYv1yvkZ7u1aOhMsJ+SptJwJ4TcueA97fk9KnGbvKa/8sbfLtpxDeYl1Za9VJsDV7JuR9Za3uEzU67VUzTjb1/u/JnGpMXYT76xMJl5hno6B34S1PnGPgl/Yf+tovnBCN13Hk7pJEO70S1gj6X+jp6evV8Sys93dneSfKvEv6bLWat8qqncKsj0WDZ7tW4GL65jOxJ/9lySJ3AvmyyjTnx8Yjvnz+97Py9sVZA7upinfHz2Hf13hxMgnwIIH8QPATnbGln9d4DhPzdw7k/Nzur2yHqHrIN4TZJUyuG/PdyZn4no9ThRB2o/mSt4/+l4H1oBLVywhdvsn/3Fmh71kX+prZrxlyu8GT7+qhnE2Kv/a0ZcxuRGbbluMetz1t7U37/qf67xSg2sbwQemc0W24qK//Or+41pKbQADHRpi5KSlXIN+69cBHat3m9X4hbJ20v9U63cXx5xLi2P3bOxnyKvNrrE/ccX+N9vela72YifHFdt6a8TZd2soWN6+SuXGmvp353++hmpmJRKi9Tbx+Pt8H6LtmkUaqojx13kNUENogBVjyLyH/sEJIz68aO9L9TrzFym3jW6XqYo35iSxxwMthjr768jS3ccXxy+XolVyrnKwRIxn/f1SrpXDl3lVkn8314DQvydSVNfFqnw7t5g5xIP1XdtBfoWrl7tvf/yna+B77wjnGMb1QeW8r6U8I9kNNFa+xhM5tjWn+79qvZL90rqN6pgg4+fdZoLzAt2P6MXmCHSv38d33N0HbLwgsl1BirIISgcu9KpzWDy3l85WxQZO5NbX99l6C+P+RuxpTSb7oZL2OGnb35KXs+3kXQl8HClJngHaz9E992yKDqCIATdmG5QbAmBfRixNpWOt8tO17F46kpN5tC49lNtJ5nSXtX+TgBAkw/uUvURpAk77ixfrvzGb5OH16+ASjtykEhMfzOjuPWn2gnqXY18ZE5fAGTU8c3n7M1wu/j8ykhsUb1DWhwxiDA+WFPZQUFK2FRoZ0/yrZhXuJZzkXZ243dKXRDTQD4+00Y+qGRaDi7FHfc1n7xLwYi3g750DipOzw/3eKBxiUKgiTc9OH238VRAkf0TpubxFXKK+0RokSlZ8OA3+3FB6RBerxaHYKgckconYQ7BIh2UfPLS67D0XIg8iofYyZgd7TN+R2fyWkGN4CtI3KryiwSeLbwmrpU7x0ogwRS0vV0PW5xJwVx2OTSM8VwSFQoNsCpu+GqLBAz+55lXn4AM25RzlJ2pCNdAQV2BuotJLIueOq0RQ4Av0YXUxdfUrtZ9tWUaM8TXOd4ImJgHoFsl0cDjPJlJQBMjOwdkC8IQeQBteR2W7SQeix4ACACp1SZB3bvJQmOpwoRAHQbC3r/sRPhmMNx0BwevIJwQ/QjU2A7m8EC2sCs93hkKtT4bBa3KYA1L0CghYvhGMjbfAVt+nsID87FhyY5yHYaoGcsqrATAsIhpiV69FtHKkREToghKoNSy880W3fUYw5voZNspwekZBw2rvaq0suIos+xBghEfjnt0KQ/jFIe4txPYD/0kzH1/y9dte1BZYQqv8iU2tjnwMedOzumvukKmb8/35jJiNVTgPNMHkiheuR5Bf79WN3GYt3AnVJff9R46dX55L7BS8xbRxa/jIXCTOxza1ZA9r4HJ7oc3JfAgvIv82jsu2arX2Y61HuoEf6Sx9rB4mvjaNhn8qmWgO+Wt9JvV5MB11/uiQaBt+BZkmjquHn4+EupkqsAQLnVNrMsFRyrGPXayYz4OnPPt1uQzY1jtk9Mty30Eensnh7bq98fwXCxxcJ94OhEcvPUp77TnCDBI8rz6Z1FNn5kTGGew/BzV8kCwFHzfJtdmFOiSMUQU6cZW9fvWkJKC1Ra9tnc4HjM3ILxge7+7Xd+h85gwtByTPMKmgzaTHMtvgmADTgfslV77YJigW6YiORjZVDHD4OQ4M7uBvGmPAPdMWxXvrnuFsY6PN+6aGOi4XvZEwvfPW7nxgoMoK7bvIEZap0/J4471tXVxCCOzer0JIPW17qPQ3/H2CLFdxNeQIM8U7VruwJT86bdR/NMV4vMhcapKsWHfL/bwuzo0pIBRDT8fVlbC7L7vimkninuTjdUQURRqiweGHQ+KWt+zQ45iF0iD6sydtb8XvVry1igocPNCAbg6+khVMrX73O4bINagswkmg88VSZdRs0+oGrJnEtvEoy0s0zvhQwcfFBLE1U6a8Rjpdw8r7NQ90RCOCm20gEp31Tlet9TEf6sjLniuzwty//aUbFF8L6in5fTxeaibhgELX93B4yZmYDKtNj1Ob5g3+XpULaeC77G22ONIQly5Rn921Y5dgfPrdxXku80ub1WfPb017Qk5nrdb8nMD/0hT6xcM7XKPqQ+3nCXDtJ/cOvWu5cFdm/QFLHgxcvbGW01cmk59XX6FxNPVvPSlitRxug9seZhsXgvwqU72qSB+M1UExxFsFJqXFkgfg+ylpGfyIOB1z5EXXXn0qbJAD1gJhK77Cgc+Zg7mBzf4rRJgCIf/eEWSx0KnU0a/8XbtiZ5QoQr7nTHt624kxbmmkTiuEjqeBBTMSA+6GkxS/drW3QrDmKRgxwvxE6SvN+ZLdb7j2MO9rLMh7AbucwHxV8yLg7hVshofoPrJ0yoRRS+XfsjKWxnzSX7rRdTIM68ERulF0oDvFke6x7ictu2dmzJ/N2ZakwicwoL2HBdT3sAQJ9w8QKWFKNarnKDuMvyK6Mrg6OhnCbQuQEIAnnjbQRciTVMqE0X0qgIQOCg4fIykBqEoZaMoigz5fZb9r1qflGQkR3MizRqUHPyu1kaNrjQXGob+GlGQuGow9QCxeGhUz5OOmYeIBlWq0G0Rrxl2A0imanz38UponIta9jF9ZLmYBNvTJw/6fML9bNLjk77LT6bTWibowtMew9YbY8MyroHN8zc04cl8tUmcv3uQu/3XI1aQ7cHJZS2CppX8WfDsOcZjtAvThRkEfD3zRJ7gvsUGZDFgnz+q9mFNGQ8PdSmCi8Y+R4l0SD60GKeaQwDQaP0K9XwY4mnJ5Cjkr7a2UHYns12ZOfugK0QuUD04ULV12hw8/CR3T7LrBQ61pkWFoXRmHluEBYVyk4sSBT0fuqq2pykZ1SVbr7jwJdE2eXrTwueEN9wDrY+k7DdFDT9Fsi5z6m50876g2Gkuua+oT2aRPMY5SUSKsVlFyR4XqB/nD7YjnIQo4HTLeHd5D1BOoOdF6FmealJCmDhACkkODxVogqcqz1Ij0ZuEYaCgTeTW3Ynf37/WCIzrD9lKZ3XdTdGKRxq6BETRTBkBBCFT1gk4G0inKk27/6EnX4jSUbsZT5jgtue0r6zJ9/m+dzW84RMiYU4ABYAUGrPNFNGJ63jSh+4bcz7pcqMynKLiEZNjofK4V9m/URVC2lH67uyEIIjq3yF6XU6uPx3uBoFbECCE/TxIcmTLouB+Lczbm9cNR4ahCSvOmOUTv+9Vw0bAvwZKuue+gn0gfHF8vcNcdC3KvQPd0+sN4y6pcKhHTQPR0iGbVI9CTOCCNERwifq2OCpaS85ceF8qoxzEsMBbgvINaZmfK+7id76BEX3wK9I1dxM8gxsInMMH0/d+Tb7V1iMhtT9n5wqqDe8H55No/EAZ8fTozrBp30RJkPMCncPqpWs4vuiXsSovprsPkqViKJCxpQ/d1P3mlF7ay5CidYZ5DmnxGwfvilEHbAjKcRSOYPow4AlGyLYPhGMa//7aeTB0Yw2pSc/ObD4/n21DPMjL8VfDl7+y2Jqeb8ij7r+llgd/eXOTXZLu/coijZGGR/fesnHxqV1/i1P/Lf6iJaz7LuXqc19HsfJOrTbPn47QKg8lzr00vNHEW3tvkR6+LSKoi5ic7sDGnwFJwpeSMj4RgHCl95mAeWisDxFVPUhVf1b+R99XXu+Ft+9sHxL+nFLdO9JNQaxP8PR4GnTKvaUnoYgf2rc9fW5qO9mvLflZ+s2nG7GEKjJHn9Xv+w8YbwDwVESN7U6ote+P2dWKqFb1BXIIHQlGF+g22B+7i6KDmIId0/zMcOVpDqwhPAUclXl+IV/oSgVgkABVheqY2ZJNpjX9dodBBEBz2Cep1TMoX5oj/6zLnBRfBDh0QPwmGFe8o5F0U/YRHjLKY8ebVIy8p4HIK0LCEGnL6NTYr/Xj9slbPUV+F+6ayEqJ4tBxgqHYA/5rvdPed98NH2GZmsZbduFp/7X2/Oc9zFNSFAtYGYTj2FHZkH+dRbG13V6qivL9A9ODuoM9CPfUZcUHx0Iw41z39SHD1T4GtPRE0NK0ZIyU0qq3OOixNaLy7VWWFIuP7KfQu/xgh3m3J+88HbGlPtiZduwTdItVrrFtMRM8/YQBVUqS706k3TZPv0FfYwG+GY9s4/JMwsdLZhp+0OTIjqdPfhNBnIGqyBQrsm9lXvOfhGgABERdxPwmebC9gODN0nxh9itECREatDofKk4kQNPbYh75+3UoHSLhfsyZRNLHbthOsGyxvdbODb8HrNMkfNxJ6BFVh265CzJ88FeadYd8337MaKejX+ELDFrS6PLkTMRsM2EeM+ZBh24BoBi8kgclOKLJG6FB19IRCc+PzwlkLJo4M7WpZY6l+bT4x8wkJbIMHTHupZ5k0WQR+1OOi4q3wa5m8P8V1ca7Ql4Zg12MgMBxaQ/ym774GA+WKBqxpcE4ggLn1RVURrfCHuYZjAIWECVjXqR5PT/JnTvBniver8CKeR4xZNQvRwKwppe9NuCD2odEasaXek0yINJD+g0YX5esg/Hds+MlbGejDu8G1hulyB14RuhFJQDbYX0UfWDAHySO3Rk0FDqLJFsfD4Ie3utncNPpGdhF7vU+mztQuNITvr+ZZrYk3ci2eLYS5Ma79eG9Q0d5RAq9FEw9ebD8MNP72u1sRvQXWbouII/bulLYdVzdtSbFTlh9bUdWIIXftfLLsssz454vVETDskytka9eh3i8PgVgTwSBJbzsG5OqEsQIY4/qUl9PYuhMkJGPgi1/7gt4mq9DEWkalXF/VDGXsHPUNvaxHqQ0tnyh+jeWX4oiMXi6oSHGwETSO8Qnjo8W/dGX4+mmrqTRMMUASajr9e4U2rj4QB1aJ7f0E+xBv784YR933hcds8ZmFblgw8/62vEfbTW/sTsJhVBY41oZxZkxsg78weh9uVO0kTsk2y9J3Qe90m9+jiHTrop0KBZsJPVGvJ1HUDX0SxlAgAiRzyI+qf7ITQnHi0Smz4cdHzO59QNdYkcys4YirYGRnPiolzCpchocLPFjHpSGfcbg6LyeIP3IumZ8Es8yxiILOOSzvk5TXXUiLNjTg4gw+Xojv4XxgHvKS8DktQqZIuK7ZLF4aIA2NqoY7A0pyX6KXd2u3tcH+JvSp6Y37cEmAvnwfM7QtXZaQNgIIjIlRKbt8a6CzSqjOvxtHJ8RhDdGSW/Ajx/UlpKvwgD4d5OGc/hcM4szyFa8+0gDlYQJrtvvzj8PpVUFHfiZ9F6xXoDzWpUYcp/ektbo/YEOLmnYJ/iLNaQGZR090BbdaDx/4crOQp64pwsoKDIqtWA8pp5DoRdm4Ku6uCn7jMianmeYk5lg0SaEjiK1W3X2SgLpSfKFx97YWxXuJMXp2wqZezo2kQHum/42iQ4jE9nHMi8MKefz+eAz/MDSTdGb0eoXz98+xVzcvD72HmZitloE19fyCmmjsPna3XbstYdRnUQIeOSk36EqTaoXK+pF8eqeVR9039V4ACrVzjJMVEwlSKF58SrR6Lhr/Gt44AP9S7J20upoRtknGR4PyVff2UoJT5h5SqLfyYKGraEKh86crrUvlEut01PFFO0ZTMnkMnOisk6oTW0D+ZbPmITJPfQPjBQVx5rpofQRiC4vhUv5d1lHELezOm1+NS6Two7XtwJZeBZUh2HrGOsb5KgPUdQ8RRT7EMjudt8i86l+yAwJzwbnaniMomRUYUJZ7pBhnUqgw90o9f0ye/iiiAjippjEKKxWW7rWPws6O8Q37WvoAVJEHkrTtArZu3QnJ6wenZoTO2wTBMMDMjjtLuyWIiug02PYm/opGsc+cnM3ctbN3xGMoLG81U7nsVGnQ/EghL/C8qx3lo0EFAXf2KBlbhFNanT46hDo1wl1CIm6L1F+2FaLGutre23s2kUQDjN4rhxzA1zADHq6OdqwPsvX6uoaPS/EozdQaTL4O+6Mov4S3GnhBxwl3A+B20+Soni6lPJM9l7g6qaOx0nhcUkuHq33u1ltEP8fa16WFEyYq2Ry2IN/tkoXskKtKhg+XB4vP6GcRGPbPUoPOjBiIxRHaZw5QTjjOyPxeNdG83yEr6ffLwpX8cwzodObMRyNtFxEgk28HmAvhkNxIX0VOxNGPHQ25/vDMh/Uxhf/Qm4ouuhjowr8kEmw+uG7c2hKI+8NIAGHZ1coJOHgJgfG3sX08G2vCyfio3V5EBszk9eI4EMgZoNuMuzR73UN5Om9JrHPTZCXLegjbfa+PlmWSuWbMMmNWDHqJ7K8z/7yQJBXhNQK6Yp9BFhbhcQrlohnGiJP6w3Jr9MWBZOqS+HoUSsmhe/WBDupclHgMn6MNUHhSCwbTQ9itQvfpsxkWEczCs6qbWpTMBCmpoOtnwWLPcZKXpUSEzTLCB9YlgXX03do5I4cBWyne2mJr0WFeir99jO5tQXWT/gJKqN2srADJhmEyzKohpU2BqnHNQnapucNdxwXY0p25JZRy6yIkVC+pkUq+3zifWyxqWRK8deeXezUMjRggVqjsWpSjZTlVNEKfLs85EiLCy7GuFP9CfdJ+f2oK4ZVD5AcrQoGmoqMbcs+rNcbljHE/Pm4zU4iP14FPGHneXXnvU98n6u+V2EQRAni25ekxxPiyYvyNhtlMd4nnlcsrKa1pPYAEdPwsPK8oPZMgJSiJQA69GRwcp1u/H/tXceyq7oS/ZozfLfIYQgYm2QyBnvyipyDyfD1D3nvc+uG6dv7nIGrPMAiSGipW72kpjvM+LXuMzMlPFN+bCyYyE93cy2IjYyP9rPrXPKIYRyciHGlWhumQZGnQb6M86sXvRRbqtvEp4IIwpe9xtaQ1NqnXazcclc4FBaviN4VDzJz7biXhxbLDUiRSpN3HrDFKsZc85aSU+XMX2NcXRN0ZFBW64xWpMnJtB6BUOsQYlMK87Sq8k5DEnRQLJAVbmzYOmMROX0+xEvEUzxSIzRnideNM+scBDw6P/JQzjVXh45pSNIiRnNkk0y326Hj3WHBelXLKk2FJ8oZV2/gZ++w0iab4UHQuzPVVcFQbaDV6gYWMdAJ2OSsoGTMBtXukKG7kkc6HlQcwq2xpeaP9ikqTqaHF0MwNeAeYbtGA5sRP1OrvERDInR3+kHijN1Ut0S9X7Kgnj1A3/UTmBVfGdH06/iIReVaU0/ay2MvP0CsRRzE+doA1SEI6/UlOCVef/z0gl539DM5OYD3kQIG2Xcd2mPYPYEJWdOJ/iAhvZ6c8gvAi176YUcSPlW0gc320mdVxucJ/Tr71HVKx4JScteQJ3iHr6kfl8NCix6i6QMRvCJws5eRjoeHP9mK3LDXW5egAWuvTHcqOnc6e0M7emFCrbBzbhqJgs6DPxfRnj5ltck4oR6aixmack2xyGai+FVT6HFvNG8PpxGEP3IcXa08Ktp8/LGz7RyFh3mdxMkYba0YaXkHPh7ZNKKBBf8w9h0nA0Q7aYDNAwjQrA2yEpYb5o27D5v03rsGtpKRMgAXC0mHBD3lAzbzrqIiHXyDQyCw1rpWHa5yBgpMcNhlGHKeDZ5EGZlX8etcyn59lag4dHSNujRj5Bp1uueDJwjAbW34iDvrSeHkBco4CSRN3xKScbQY4KGrKnmA0MzkaPIx2ps3iye8+5PNhCV9rSgvhxkMBHFiK9PUn9m9m0ykOw0U5ZoY1l4UVwGrRC174Xxh0DtIuexLFliw98jXPATebixuzk5iXXO34DVUkkhysCOqsLNqBt4g9MNV8GhTMppMwA44r+TrFoSxw1GpnAOSwyrOuQBTs9UDXlxKGJjGTSnMMM7gwEaHXsEUjZ+Gx17iqyt3TUGy3R25brJZVFSYQU/QCwgV05LBacAXJHl9wS1EDfBTgyR3N9bXbgNtd31O00ATXl18EaO7rIGuOHdA3h5dc0LjfWdpUaJKyL4ZJw6qEa1Itif+MfKZvBANzBhjHmXPZtmveliCYDVn4nKHSomyRKuZqq3SE/iWVmupKY/q/jwEAQ8OQ3AvuDLQryIYM9pFAEs8z3Vdsd00HD0HnK2grp4vQ7cxQKiUyRhoK6/PW8HfL5ewts/5AHJ2PIG2ENJpKbOSlISEvcPlfk77U2aK0PwAo/HJ286jBwr4TJAs1QWTxjZFPyW6k5TpLACdTslJVujzKwWunjoUwSzzK8dALjxxBPFU7jHvhxQtmo+dzAb032glbHKHUkqZzwqVTopF45oXhWyjFkrX6QbDM6RrFgFmEyT0yiELMyqaHwOAKkCclLMMqGpBMK4O1inxugHEl+NLvjQRndLzNMv1lAVBkAi/tfCnQLKk0auE5FteGRYdWASD1x26xG1GX4uyI9ApjJ+r3YUmQtDEvRMfoJ4JEElbLII6rGF9kztrAZukn9kpgyBYfW0FbmNtQSWZurEhcUvpYFktnOTyfaKyQKG1Gml8zaBdYQvcfbudUzxRw1WSr46gZrbRiwuWUSRnrChVKjP63D1vPLfk7VaMPXH3Iq+6haeKIhFJW7fU9KyXyui41+gkZT8w9KkAKjgJay3sGDCblRbhDdueGGGIYF62reqpQ3BdR+iadWvUO5jACYxx2YwqIGFwEuPehhbtaQua5n4XniNI48BKaMjTM2UPB+HPiEQnG/2CkUAvWIwBvqdjMShgC/o+4aVrpSQ/BafuAEHq4F4gdna78eSM0hCO4DPYlTNzWpDC9gIA7A8WbFhIh/mvNEVIoBVnpOxEpMYoMd8QHFBvwZ3QkXSr5kGA9wWY0i7vVKZGwz3gBfyk70wLFTbZO7x1yyPVJU6KKNYnaBZ5eLNO6rYgBZBWM8MU3ABZUF3uhpmJb68xCP1+3giXFK9N58qaMgaZTZw33DKmq/oZ47R/cvu+JFVrdRLoXRCX6Rz34gxT92MCxizFXhDNvNnOojjJIe7xyOtU5UC4Cbf4k6kPU25y5nTTazovpvA2606fYH3oJtO869RjWoaptW2g22YvaMKEYCLiPEIqYS1c6tZLnBy6zGxbh+ProG44mHQcI2+XXW6gAT+NMtwY8PSgTmBBCugia481S2fmxLvjzBT4c9MhaKJO3ujOJTCGmoz1LzzG3VgmFgSo9IvShjf2bgWkIcFgCXaG9hliCDZXAiZ8sPktu20Yfz1Y2H3jH+dl1bePJZKTpG9+Lt+Kq8sedANzfERsIdB3j48L0vLWNjawrzlozrb0VC7TJJ5JoY5uXM/xOFIC1Ykjs3UxN1FWdb5uLrTSCxPgPGDh81npDwHlsmoJTzvlD9iaITyGqoWcofGtZrEpM8y5kQNxIjNNuYP5cgzTCsiqhTs11Xgu5WvWMhOUIHQ8em5fY6LgkoaLQCoMbKiF1wpAdCZqmtQ0GOnpcc5HsZHBi75sKolSe9VOgNqJinAHW5Y9wa4vI90Q3F1hG2zUWPvjvQtudeQLFgESHWrjNFe5vFkltyXacwkfBN/o3vRIXCEDY3tRiepESXNDcNWoYs1BB6eT/TphNtIDIoxdMFOGATEZTQfn+1JK0/QHenr9jvoOAwfq/D5uRlCCIPBH0Rz3Y7z+pQjlf6BcvR7a7xioIFka9PMs+nHH9vGXgj7+Lnk0Zj+LPh+axYeW/awHpT7v84ePgvTPJwMP2o/6joN65eKq+ln96xiB8ujjHg7Z1Hm/234fsqXG+CmSev/5fO7sV1P8cdlHwTBu1WfBkPlRuxzH0NEHkT9kcfT5Jxvro7ITfBwOY9+WMddWbX+UNG1z3MxWfhBXejvkY942R3F4dFx8nGdBh+WhXyn/uCBox7Gt/3IBU+UpODG23VHqf/778zlH0zrQynpNe7/L/qiH0I//iOKuj0N/jKM/unY4rvyv3/fgDdgkr6qfbfyBoMC36oyAt8qPGz7bsMTD+P8BmyL+BjaGk/9CG4H+DTYOfRHW+BvrL8OaRKF/gE38WrCJN9hfBjaM/x1sHPnFkk2+wf42ycaB68avBJt6g/1tkk3A/7bQvhVs+g32l4FNEL8Z2DDyRvvbRJuEfjXab+71dWjj9O+GNvZG+/tkm/zVaL+59hfKNvy7of0m298n2xT2q9F+s+2vQxvDfje033T7+2SbRn812m++/XVoo78b2j8re6P9DbINQ8ivhht+w/1lcCPE98ENHEbadvzLuQvokWsbxeCK/wE= \ No newline at end of file diff --git a/assets/drawio/121.best-time-to-buy-and-sell-stock.drawio b/assets/drawio/121.best-time-to-buy-and-sell-stock.drawio new file mode 100644 index 0000000..f1772c8 --- /dev/null +++ b/assets/drawio/121.best-time-to-buy-and-sell-stock.drawio @@ -0,0 +1 @@ +7Zldb9owFIZ/DZed8k24LIyxTV01lUqbdufFTmI1ialjPrpfP4fYSWwzoAyBWoULiN84x/Z5zomOzcCd5JsZBYv0G4EoGzgW3AzcjwPHsd1RyH8q5aVWQs+rhYRiKDq1whz/QUK0hLrEEJVKR0ZIxvBCFSNSFChiigYoJWu1W0wyddQFSJAhzCOQmeoPDFkqVuEMW/0zwkkqR7aDUX0nB7KzWEmZAkjWHcmdDtwJJYTVV/lmgrLKedIv9XOf/nG3mRhFBTvmgcf46ctXy394hjNrdAfCO3hzfyOsrEC2FAsWk2Uv0gOogLeVI3mrIAUXxynLM96y+aU5CWERQcWvYkozRHLE6AvvsG696QsPpR1HSo2iDDC8UmkAATVpzDUjfCeYz8SxZAAGwo4IP1vSkCZKsqQREk91vacZaiYkDLmBZogBmiBmGOIXnWW30hbOK0A5/wmqZJQ8oQnJCN32dq3tp7kjg/stQD3I4liorq0acqzLQnXfefYd9O/RoELNUHhZUF6ffcezOBaq52nv5gu/Uv13nn0H/XssKF9PY++yoAID1NAgxdCG7cs5gS/GWaZJIMNJwZsRx4i4Pl4hyjCvAG/FjRxDWA0zXqeYofkCRNWYa17vco2SZQFRNfsqm2NSMFHDOrKtZf1ksi90qrHRZm/wNKW1CkVmYSe2vB2xpdc/3TBSuL0Wkm2WknZPydEoucNrYzILSb/H5Iy0V+XVMZmlodtjcrXdXIPtapjMwjDoMXkyemU2udfGZJZ6Xo/J1+rDxv9Xw2QWegMnyCoqEK/4ZcK2K6+lcgGKRvPHNl+tzx+xxqisatdHnKNBdVhYSctq2aCA/HteT2TOSPQkTfHZKtZaWRn3LQbMcHobTK0zlZxDNWA82wyYpsA5d8T8umfYjmYPRZw+/8xvwOPzOj/t+BKCMt26z1bpdUFxz0GAwjjatXkPohD9jo3Nu9d4WdsA7nD7tfaE2t795JOzYHTA0Pm2hDvBm1VsD34veO3I1PZHH/wT0etH6qapk+HzZvuHSN29/VvJnf4F \ No newline at end of file diff --git a/assets/drawio/122.best-time-to-buy-and-sell-stock-ii.drawio b/assets/drawio/122.best-time-to-buy-and-sell-stock-ii.drawio new file mode 100644 index 0000000..04214a6 --- /dev/null +++ b/assets/drawio/122.best-time-to-buy-and-sell-stock-ii.drawio @@ -0,0 +1 @@ +7ZhNj9sgEIZ/TY6VjLGd5BinSTeHSpVStVUvFTKzNlpsIkK++usLa7CNve1mtVWiXSUHB15ggHkGZ8IIz8vjJ0k2xWdBgY/CgB5H+OMoDKdoop9GONVClKBayCWjtdQR1uw3WDGw6o5R2HodlRBcsY0vZqKqIFOeRqQUB7/bveD+rBuSw0BYZ4QP1e+MqqJWJ+G41e+A5YWbGSXTuqUkrrPdybYgVBw6El6M8FwKoepSeZwDN75zfqnHLf/S2ixMQqXOGZCK5NdJTNEPsfqWrVLgh593H6yVPeE7u2G7WHVyHoCKzowjda0SlRbTQpVc15Au1r2BDpzYrgo1e9UxAqIEJU+6y6H1Zmw9VHQc6TQJnCi2980TCzVvzDUzfBFMTxwGNv5QYu3Y8EOOhjOxFTuZgR3V9V7PULMgawgnPUOKyBzUwJAudLbdSo9wXgAqfCWorZLiAeaCC/nYGy+XSRAmTYsL7ugNQH2WxblQMfINhcFloeJ3fvqe9e/ZoCY9Q5PLgopup+98FudCjaLeu/nCr9T4nZ++Z/17Lqi4f4yjy4JKBqDGA1IKjupfZ87iu2ec9yTCWV7paqapgdbTPUjFdAY4sw0lo9RMkx4KpmC9IZmZ86DTXa1JsasomNUHxryolM1hQ1fvnPpAf+bzJnTMRHB8afC4AdiH4k5hJ7aiJ2Krn/90w8jj9lJI4wEkdIMU9iDh8ZUpTQaU4hulcNp7UV6b0nRACd8o4d5fuYbatSi5X8UOpuSGKULYP0z42piGdxzRDVPcSw4b/18N01M3HAk3VCjb62KuHndeS9sNqRotTpHuHOshQQpbk7h+ZSWMzE2hkXZm26Si+rmuF7JWInvQ36uVs6eX7JlsZW/ytxg148UsWQT/Kekc+1EToWHUNDnOK8NGV9vL0fqvRHvDjBd/AA== \ No newline at end of file diff --git a/assets/drawio/124.binary-tree-maximum-path-sum.drawio b/assets/drawio/124.binary-tree-maximum-path-sum.drawio new file mode 100644 index 0000000..142d7f4 --- /dev/null +++ b/assets/drawio/124.binary-tree-maximum-path-sum.drawio @@ -0,0 +1 @@ +5Zpdc9o6EIZ/DZd0bH3Y5jIhtL1oOz2T6bS9VG0BmtoWlUUx/fVHxBJYUnJCONSC9iZjrWxZevWw2t14BKdV+0aQ1fI9L2g5AlHRjuDdCIBJnKm/O8O2M6Ak7gwLwYrO1DPcs19UGyNtXbOCNtaNkvNSspVtzHld01xaNiIE39i3zXlpv3VFFtQz3Oek9K2fWSGXnTUD6cH+lrLF0rw5TiZdT0XMzXolzZIUfNMzwdkITgXnsruq2iktd9oZXbrnXj/Ru5+YoLU85oFtI+bFuzb/xH7MpnRxy/758GtstucnKdf2ihu5NRqocZTcqnG7WTJJ71ck3/Vs1IYr21JWpWrF6pI0q24P5qyl6rW3c1aWU15y8TAQnGc5zXNlb6Tg32mv51uGEY5Uj54OFZK2Ty403sunsKO8olJs1S36AYC04ho5rJubw/4Z07K3dcZGNDGL/cAHUdWF1vUlGk88jcG1axxntsYxCCyyeX9PZHjtIsPo0kSOPZHHxlGfReYziIYntmgotGbA02xyYYpBB7M4tGTQd5jnpCzIjzlJ0IWpjPyjH1+9yrGtMohCq4w9ldNrFzl1HEZ4kZPffC4FkRk5MkOMX+HAQqe/9TALE2WlTsAAwsvsZ2XJtcuMI3RxMvuJ2TmT37O4AGyJhuPgohmn9CcdaNjJZi9BZj/VuvrgDDouQLmE4DL72dnVlw28+leWBZf5kYzusjytGwXgSXg2/QTN16wubnZVbtXKS9I0LLeloi2TX9R1pK+/9ux3ba/jbmsatZr7F3PbrvG133N46KFlnuomRguvmO7orybP1yKn/3Uq61BHErGg8tlg1N/RI/dL0JJI9tOe8GObqN/wkTO1lMOvDNvApPEeGDNIt1T93AEGbyi3zpck3lCdGN5QD1ztl/4/UPMDzr8CtfRI1OATzmMY1BBKLT5QlJ2KGjLHmxkKgaFR84PuAVA7IzQmMnseGhASGpg50KQnQwOxPRQG8cDQID/pOBma+OX+KQrkn45HDQZFbeKglp2OWuKgBgdHzU+8BkDtnIfa0fFTFvRQixxoJqdDkzrQoMGh8dPIv8E/mTrl8/4pGoVELXb4OD1+ck9VjAdHzU+lT0btmkL1Y1HrirrBUAMOH/HpqDmnKk6GRg0/Uh8H6NU3VhOxHUtB6bgiLavW1XhF5HLcrCsPRUlbacNn175qXlOnUKZNpGSLekewYoUq++2uvsNyUt7ojooVRflUuUjwdV3sikMPIM55LfW3hcC0e9W3dHaTzM5VfUvskn36yD9HYQJ8AtHL60iqefh+sNv1w0eYcPYv \ No newline at end of file diff --git a/assets/drawio/125.valid-palindrome.drawio b/assets/drawio/125.valid-palindrome.drawio new file mode 100644 index 0000000..706e839 --- /dev/null +++ b/assets/drawio/125.valid-palindrome.drawio @@ -0,0 +1 @@ +7Vpbb9sgGP01eVwU40ucxzaXVlMnTevDpL1UBBObBRsP08bZrx/YOHFMq3ZSU5iWSmnM4TOXcz7gAzLy53l9w2GZfWEJpiMwSeqRvxgBEAFP/lfAvgXimd8CKSdJC3lH4J78xhqcaPSRJLg6MRSMUUHKUxCxosBInGCQc7Y7NdswelprCVNsAPcIUhP9ThKR6V6A6RG/xSTNupq9aNbm5LAz1j2pMpiwXQ/ylyN/zhkT7VNezzFV3HW8tO+tXsg9NIzjQrzlhbtVhG7X6edfN8u7nz+mi9nT7faTLuUJ0kfd4UK3Vuw7Cjh7LBKsSpmM/OtdRgS+LyFSuTupucQykVOZ8uSjLg9zgesXG+odui/dBrMcC76XJt0Lnfb7QXp3FCDUUNbjvsOgljw9lHxkRT5oYv6CJGCQxOyTFDlGku8gScA1TwocHG7ANU8KDZKgdZKGc5I/sUxSZJC0tk9S5BhJUwc9aTgnWScpdpEk1zxpZpBkcFQyUgjMl0+yj5Wm4hDzKdoSWGUHDnt8VYKzLZ4zynhTkh+DtR9FMofCNaZfWUUEYYXMQ1jVIDMUuUQGqXcDgzUTguU9gytKUpUhmBIJ6tShHNm+UjU/r1MVvY/zCkE8TnDJMYICJ+OSVdLyoYmkpf2GUNpraBLiOAneafIA4Ynkgak4+EjFu2F6kfxskoOpY5KbMexF8vOOcjmzW9bcDMkvmp93mNvX3IwL7W/D/NixbZhnBob2d/SB7xpLZmToAEuu+RIwgyn7Iy50zZe6LnzMWrSOwyCc/ANr0SZGGKF3GhnBYC3q0rbWok7ii+bn03zmmuZv2Gd0/JG8uaDqq/q8Aq8K10h+DdE2bSbaPtnNnzRpKruqyvYiTfkJ7BIbUisPu9btWWRCqBu4K8UEWKGk8MYEsWJD5BTOx0jWCFYJFFB+KbyS3wvItzcUVtXDN7xjfCvLAysPxLX8yCeIVIuVofQFjCir8Lgs0r57v9fyGA4cYmI6RPCMQwRncwhzE2L/DG4YkFo/gwPmxYD9M+9hQGqfJfNmwL4vDQNS+yy5eDUwDEjts2RuAc8YnPyXhyPDgNT2GSi43HScXfLZR0kuk8ffFzV5vR9p+cs/ \ No newline at end of file diff --git a/assets/drawio/129.sum-root-to-leaf-numbers.drawio b/assets/drawio/129.sum-root-to-leaf-numbers.drawio new file mode 100644 index 0000000..337fec6 --- /dev/null +++ b/assets/drawio/129.sum-root-to-leaf-numbers.drawio @@ -0,0 +1 @@ +7Vzbkps4EP0av2zVptCNy+Ncs5Xa3drdqUoyeWNAtqlgcGE848nXrxiQjdQ49rgwwoF58KAGBPRpdbeOGibkZrH5mPnL+V9pyOMJtsLNhNxOMEbEc8W/QvJaSlxKS8Esi8LqoJ3gIfrBK6FVSddRyFfKgXmaxnm0VIVBmiQ8yBWZn2Xpi3rYNI3Vqy79GQeCh8CPofRLFObz6imws5P/waPZXF4Z2V65Z+HLg6snWc39MH2picjdhNxkaZqXW4vNDY8L5Um9lOfd79m7vbGMJ/kxJ/yYfXann91HL/707fnfq29Pn8iX33HZy7Mfr9UHXuWvUgWiG6Ft0bh+mUc5f1j6QbHnRQAuZPN8EYsWEpv+allCMI02XFz1uuqcZznf7L1rtNWFMCKeLnievYpD5AmsUp+0n6r5sgNDHjGv4SBlfgX/bNvxTkNio1LSOxRGgMJwiwqbRnF8k8Zp9tYRmboBDwIhX+VZ+p3X9jy5jIpnbEXFnqphRA2rmAIVk0tX8dYJ9EXHDOgYqjgJrwoHKlpB7K9WUaBqlm+i/KvYtqrtx5r8dlPbcfsqG4m49a/ysKLxWN+zO+mtJc8qb4yHwE9ryhc3n66zgB/2drmfzXh+aJBDMI8EK+Oxn0fP6u02IVhd4Z80Eg+ybzhiW7OB8imrk+r+Xutna2OyI6R1VKoBdPRmT9unPt3EnPZMDL3fxKyemxg1aWLIdlR3RMipNkbUjpjTqY25wMZov9IXZvcrfZE+oKYxq0WNGQmuNulZcEVokNHVNenSmNVS2GS6S6PdujQEp2RDiJvukXETWROTVua0FDgZMxs4EZzHej2LnBbrmVeH81J26aGTUlXJ2DKtZDgxbZOQMqJk5vRNyfYg8xPp8w7HGKOTM31MEnxiJkM9ze6cbgkANEwG4HgzY0ZTGYmGjK/eiakMlUs8W56p41QGkgAwZAjvnauWpbr8JE24Fh8qkR9Hs6QwT2EIXMivi1gQBX58Ve1YRGEY7wtGWbpOwiL0vFnZNE3yWpC5vyfir5JXS1+4paCDkDofkr6gZly0wbh0X9NezPEgStAdDA4mbXULMcMwyY7rMMHVl8HBhNy+4QQpJsh8Dg4mhvvl9GSIraMEp9mDg4nafRtNkA2R1x8yTkyvDDCOE6RgtpOcIQNF9DVjZBooSONQb8zKMSN9AwpSQX0rALPxB5XHoA1uqFP+DENio4HZuCySElm6mlmDbXarZjixv/xCMNI/NcOZOdTyr8cGS893kKYrR7uxUh0wLm3C1E6OLtZhwPZcraszU3UEsgsnm9oFMcLHm5rREgrkeLp9WO6ppsb0rgju1tSOIUjMLnA7vUtsJEa/VHUY7V3EJZBuGELEJXuA6mi9C7UWSBn0bhLBrrwbJEKGEEilUz9cwI+NmprbWiBltvFACqmcvlWKIad3Hh7SKpdfK8aAmh3TaoZEzOVXi7n9UzMkYgaRr7Bjo41tMtrAcWl7pyY2FuiKdswQtEhGXVJic7SpGSWjGKG6b7JPTWyQpXfldZvYUMgQFM0JFsBaRfWU9VuxUYiKndZYrlTMc0Bs2kpqJoia1rD02VZ774tD1oJoQNZQHKuZMLZ0EF0IoYRLWYbE54KwsYZTgdDaQTgiCDgtajeMQtwwCvHZRiGM3HQvhGOtmkjbyGEEZW1zJwjKJEGd6esBke5AHEvZMLEBEWg8HjJIL1s/xXEsdcM200MiMo9j4wuHAEevNiDHWjhMiDaDlYvOdRy9BhzpuegTaVo/SW50HMcUR8wsARdhHsmGFyfZNRJhkhW4PawX4ve/4otuxVfqxM+f3J+Kf3+vF088W/UY1Dp4GsjO3ZV919bMA6uQEgYhJY0zj7NB2vCS4gjpuyraVEgdfD5IRXP3vcSSB9p9dZLc/Q8= \ No newline at end of file diff --git a/assets/drawio/130.surrounded-regions.drawio b/assets/drawio/130.surrounded-regions.drawio new file mode 100644 index 0000000..e6a32e8 --- /dev/null +++ b/assets/drawio/130.surrounded-regions.drawio @@ -0,0 +1 @@ +7Zxfb5swFMU/DY+rwOZP+thk6TZp06p10/ZWUXASNMCIuE26Tz8zTBLsVerUsns0rS8NF0PM716fHBuExxfV/k2bNpsPMhelx/x87/HXHmNBGAT6Xxd56COzMOwD67bITaNj4Lr4IUzQN9G7IhfbUUMlZamKZhzMZF2LTI1iadvK3bjZSpbjb23StXAC11lautGvRa425ipYcoy/FcV6M3xzEJ/3e6p0aGyuZLtJc7k7CfGlxxetlKr/VO0XouzgDVz64y4f2XvoWCtq9ZQDrtaJ+vK6fLv88e5mv/vy+fzqVfWK8/4092l5Z67Y9FY9DAgaWdRKtMt7/UUdzcDj88PF+HojT7cbkZuNjarKoZFq5XexkKVsdaSWtT7hvExvRXklt4UqZK3DmehOrnfci1YVGvx7q8GtVEpWJw0uymLd7VCy0dHUbB3Oo7vWdD2v9uuuJM+qbZaKs1w0rchSJfKzRm51y5tf1aHbr4qyHProMe7788Ul666q0AeYPtSy7bI5N7B0R8T+0TQEh+TqUSFkJVT7oJuYA1g46w8xAyIKTX3sjuUVmdDmpLKGMkpNQa8PZz7mXH8waf+DEgicCvjmlMBuUyhx3aRZt73TWMeJTrdNP/ZWxb4rhJfgxAM+4hQ/DVM0FSYGiinGwuTqCQSmEKyaQlBMYNUUYWKytSkIiDnFDqePz+Zk/S6uZpnIMudXXe+5nUVh5E8jZ+Rkk3+FrK2A5GRnmGPblkByTueYnBwNpP6tGPIEB8ouKHJQruP/VzSNHC3oLMERNXJQoPMEW9UYtfoH7kzh+YN1AlWjBwU6V7A1ih6UO1nAAAVXUa73hwDFxpzCiJoTqJNP0DiBOvkAraAYqJMP0CqKuU4eApRVUJFPzcm15RAmKkHj5LpyCE62QtGDAl2/txWKHhSoK7cLilzJQU25XU/knKZYj59CochBgZpyR6HIQYG6cmbfiaW+se+acoiRl6BxAvXktkLRg3JNOQYouIoCNZs8GD/JRj4d5qBmk8dooEDdZghXUaBLwCFcRYHaTVujyCfE3LWbEDbK1ihyUCGo37Q1ih4UqOG0NYoeFKjhdDSKWsxD1Icz7IoiBwX6cIajUeSgQJ25o1HkoECdua1R5JPi0HXmF88GtZK1OnlU8fKS679ptIseIKhjt7WLHhToArGtXeSgItexQ4CKfbDHNiJQxx5HaKBAHXsCV1Ggjj2BqyjQtXRbo8gny9EUjn1Cf2VrFz3AKZz8hABtTaMHCLr2bmsaPShQJ+9oGrn4gzp5R7uoQcVTOPm/qV3kAEEdvqNd5KBAHb6tXeST6xj00Wxbu+hBgTp8W6PoQYGuydsaRQ/KdfIOp//vSHvJd6RF3LotQ/2OtNidi3jRPOC+F+nP/vWd5nRX5zrBzP8k1hrI1ikRjUM9lvBTvibk5MxObVXkeXfu+e/G6NCdvt4662VerciG7ZN0JsuLePlCr2Rgs/OzaJQ8/pvkseH2zWn2+GTZcydI/7P3xOwlyXTZ05vH11/+2nfyElG+/Ak= \ No newline at end of file diff --git a/assets/drawio/15.3-sum.drawio b/assets/drawio/15.3-sum.drawio new file mode 100644 index 0000000..558abdc --- /dev/null +++ b/assets/drawio/15.3-sum.drawio @@ -0,0 +1 @@ +7ZpRb5swEMc/jaXtYREYHMhjoUn7sGqVsmnqXiYHnMAKGIHTpPv0M2CSgGnXbgk4UlIphbPB+Pc/uDsTYLjx9ibDaXBHfRIBqPlbYFwDCCe6zb8Lw3NlQLpWGVZZ6FcmfW+Yh7+JMNbd1qFP8kZHRmnEwrRp9GiSEI81bDjL6KbZbUmj5qgpXhHJMPdwJFu/hz4LKqsNrb39loSroB5ZH0+qlhjXncVM8gD7dHNgMqbAcDNKWbUVb10SFexqLtVxsxdadxeWkYS95YBfN3PrR/7w9Zt+n1t3X+4f5+btJziuTvOEo7WYsbha9lwjyOg68UlxFg0YziYIGZmn2CtaN1xzbgtYHPE9nW8uacKEilz6at+lEc3KcxnWTNNm/AodMSzJGNm+OCF9h4m7F6ExYdkz7yIOQAKs8CwDwRGqLJu9UmNb9AoOVTJFRyy8Y7U7+R4g3xAM38FTl3DWpn8FegRQ0GiTkjChDkq17eiQoARJG56RrRYjQ2I0vB8ZivmRKTH6z4fXMRgp5kdIxQeSqZgjyUGw9q0hISnmSdbRIS3DKDrIDZa2RzyP23OW0Udy0LKwkYm00wRD3dKG5WqreIe2w+HglCYqUmoHxMEp1VWTUqlVOyQOT0nO0of3pXZMHJ6SnKYPn161g+LwlOREPVnHuQSKT5k1aTSjXEIT0gqJwoSjcJXwXY8jItzuFABDD0dXoiEOfb8YphN/U6CXqvF21X4EpfRWeY5koawOoeDJhJKrhZzy+fncdlGsSzHdljPOfiWTixdJpZSGBeTpE59kXutUr7QVAH2cBzuafxMzwgsS3dM8ZCHtVPBzq8OCMkbjDokZTbs8gV9aWlx5vF0Vy6WjOPcwGfkkzYiHuTOOUprznj/LpUs5RdY0x53BfpfZ4AQ13QLKdzLs9ZH7hjXLi1uc2i1M5dziLAopU7M6Vqn7TVnOopZSAFTtvkpXUypwOod6SgVO51BRqcBJrqkuqfqrqboJu9599pqsQ7m+knS6ZGV9J+sG6rqbe83L4KWKU8Ax2um6Co4hv1FiGzpfxx8AcoqM1C2/+PBu8R8CxLvwoTTz47sjAKc5Lj8dr51Q8XeCWPCKstXnOMqilrBWVyjYvT3sJxbIFQaYWuDqGtgTMDWB4wDHKjeugD091dOAc7bhwig1V/2R4CNi++ZpkgVj3EwWupZizY4b33q/e/Dd/Q/ayraDXwUa0z8= \ No newline at end of file diff --git a/assets/drawio/152.maximum-product-subarray .drawio b/assets/drawio/152.maximum-product-subarray .drawio new file mode 100644 index 0000000..6ae7f16 --- /dev/null +++ b/assets/drawio/152.maximum-product-subarray .drawio @@ -0,0 +1 @@ +7Znfb5swEMf/mjy2wja/8rik6TYpk6Z20qS+TA5cgjXAyDgN3V8/EwyBOG02iQRU9SlwPoz53Nd3R5iQeVJ8FjSLvvEQ4gm2wmJC7iYYIzL11U9peaksnosqw0awUDsdDI/sD2ijpa1bFkLecZScx5JlXWPA0xQC2bFRIfiu67bmcfeuGd2AYXgMaGxaf7JQRpXVx97B/gXYJqrvjNxpNZLQ2lk/SR7RkO9aJrKYkLngXFZHSTGHuIRXc6muu39ltFmYgFT+ywV2ePf08LB6Xn79sbSe7ueLmYxusF1N80zjrX5ivVr5UiMQfJuGUM6CJmS2i5iEx4wG5ehOBV3ZIpnEejiXgv+GOY+52F9NVr5jO5YaWbM4btnXfgBBoOx6ASAkFK8+GmqAKaUBT0CKF+WiLyBEM9Yiw65W3e4QMle7RK1oOY4WihbJppn5wFEdaJT/g5W8B6zNJtRY0dQbFisyqL6F1TqPtQdIyDmCVJ+fgWRbl9KeAYkMDslQ0tCQzP15M7yUsDcySmZxsAeHdJzrB4fknk/1GWepBLF4Vs+Y1xm9bgdKbCHNo4bhq2k/5amacBbTFcTfec4k46kyB1BOrgZKrky1LssjhxWXkicth08x25QDkpfxofqsmUctLStXnhSbsqm7TfKAwm0ImYCASghvM54rz1/7/sosQZY1m9/jnrbEUbSnZrDxqQp0qWBPjWAntFCG/bHlGpFXU6pOFc7vDJpnVfu6ZkWphGOoIQV/HZzqBNzAh9W6rzztdHBjy+TtXJN3XTfawFnaADe3Wn/AHfBD+xRwH6+I614IuDc0cDPntxXeJ/Am6aGh5I6cscnduZrc36R/Fe0b9AfXfq+lHJ15g2vK5Kjrue45+kh103GVcuR9RPty0SbOyKLtv1nXTrz99ZRa+9g53tjK1Ik2uFWmbtCwfdnF+Q9dqPCJrrglZvMlfTxaJmRkWsbmn4odLdv+u9KywX9wLeOPMny5Moy8a5VhdXr4lrUfa30RJIu/ \ No newline at end of file diff --git a/assets/drawio/155.min-stack.drawio b/assets/drawio/155.min-stack.drawio new file mode 100644 index 0000000..9653e71 --- /dev/null +++ b/assets/drawio/155.min-stack.drawio @@ -0,0 +1 @@ +7Vtbc6M2FP41nrQP3kHiYvMYO077kk47mdlu9k0B2dAFxIKI7f76SiCBQSSbbYiFd8lMYnQkdPnOd46OjpyZuY4Pv2UoDe6Ij6MZNPzDzLyZQbg0AfvLBUchcM1KsMtCvxKBRnAf/ouF0BDSIvRx3mpICYlomLaFHkkS7NGWDGUZ2bebbUnUHjVFO6wI7j0UqdK/Q58GYhVw0ch/x+EukCMDx61qYiQbi5XkAfLJ/kRkbmbmOiOEVk/xYY0jjp3EpXrv9pnaemIZTuhrXvA+fgV/Pc03H7d3hvPwGf2x8sjcqXp5QlEhFjwX+snpUWKQkSLxMe/GmJmrfRBSfJ8ij9fumdKZLKBxxEqAPYoOcUbx4dmZgnr9jDeYxJhmR9ZEvGAZYg6CMxLBfaMAIGXBCfiOkCGh813dcwMLexDIfAdKCwUlY3QgLXWDtFSpBEeHErB0w+SO0eKAa4wMJgAUnMbHJsvUDhO8ANdkudphMi/B6kaAk6XiBLTj1OWTudCOkz1K72R/sFtALQ1LN1BqgDkG/6QA5WgHSo0xuYcy5uz3lzn4lX2UUmMEQRVw4fjw6wk/3+i5fJQHZduBYLMXXdichXbY1HBUAQ0n/jU/SbOSF6E8D702Tmz52fGTwLQsPPACW6so3hxOK2+OorQNo2hNIpKVw5g+wsst7zqnGfmCT2ocb4kft3WNPIdbtVqwrxziO0phCyJF5uFX+HSKsh2m36SaquYTJdo9OpSyDEeIhk/tCfcpVozwJwnZUl4wPte12p1USxXvnWYEOl1BS+nK6nRVgaF0xfiAjifNUt4gf37S1rI70kJQvyFv1WdD5RrZ/89uaUon7E5JqhCcmTRtU7pNwYQkuMNXIUJRuEu4XTC2YSZfcQcReii6FhVx6Pt8mF5f0/ZGQ2xsTndjW0DVw1g95ITv5WCkPxlbWqALFNAfVMKeQ4p+qFR/Mwao1HPKG5EafLO33S5s+vOYUD22XNRujw8h/dSMxkoPciz23AzMC3LcASMEidY3I4SKnroiBMsBHXu1O4R6dYAAFRLXOYqhIwRHGcpsX2i8U4igHlB/7BDBVtw57HHn540R1LNvhPOcW1qAEvbBBnYijv9j1lKL87Xgt2erLUnoPC/vDq9ZA+CkhxItWc+edvwzxiipusUcNJKmDFrmHCIc45iTMeS1eYwiNj4rr8u2BRfu+Rt5QIqIv1GkPqK47km+8WGgeQ7UTTnnfwq+EuZTaJEl9YzjkD8zxnmBXDZPNww0+1EaD5++uF8GzkCbPLS68bbrKLYEzR5jkomx4Y1JTYTsMow4fIPbU2Ub65pVV8ykrlglijn8yWOelo2NAHGCJYTb2nbLr+mhQRoyXjE2Xl2A6YQ0x9G2azdwMpvvNRuwVPb6ngvHM9uNmgkrneRolXISMt/emuznfZRVa0Goyu5J9Z41WjDVpE5OkfdlUpVldb43AzQHdvLLX5NNvWxT+hWlZunkruaHT6/Z1tjyX96Q87QMPd7Qkb0Ctj2z2avGXRm+3ldmX/XP1l0N0R6WicsVXMoeC3uouNhcO5uBzn/Q7PiInu8c1AfC83BPTXtO/rzXn9sysaPNTaj5w8mfl0alXqTp9uhqXmtSVa+qgKVdV2r+a/KAwgN27+n0G5aaX5mU9YyyRmBafd9vmQLbnyCwdXpuW88b2FpqomLi3k/BPVf7ocpSMy8T935M7nUOicA27HcjHys2/7BYXcE3//Vpbv4D \ No newline at end of file diff --git a/assets/drawio/169.majority-element.drawio b/assets/drawio/169.majority-element.drawio new file mode 100644 index 0000000..d7ecaaa --- /dev/null +++ b/assets/drawio/169.majority-element.drawio @@ -0,0 +1 @@ +3Vrfc6M2EP5rNNM+OAPCYHgEAnfXaTOdyUPn+uLBIIMaQBTk2O5f3xUIDMa+NDc45uokRlmtfn37rdgVIM3NDp/KoEh+YxFJEVaiA9IeEcaWasK3EBwbgW5KQVzSqBGpJ8Ez/YdIoSKlOxqRaqDIGUs5LYbCkOU5CflAFpQl2w/VtiwdjloEMRkJnsMgHUv/oBFPGqmJVyf5Z0LjpB1ZNaymJgtaZbmSKgkitu+JNA9pbskYb0rZwSWpwK7FpWnnX6ntJlaSnP+XBn965ktZJNiO9M+JGv3y5au5WrSTew3SnVyxnC0/thCUbJdHRPSiIs3ZJ5ST5yIIRe0ebA6yhGeprK54yV6Iy1JWgiRnOag5W5qmrQhhbWuGJAxBPl6CXNUrKTk59ERySZ8Iywgvj6DS1q7kClp+6c2/+5OxloZUSXqGapsFkh9x1/MJQihIFN+D6AjQMaLQDfCXvI1mUBUNqbf0ICwwBWIdIY9DJPqIXQBseSvAtNkDtpoXYMsRYOq8ANNmxjB99oDNjGHG3AFbzoxhq7nvYcuZMcycO2D6zBhmjQAb4QUBZiGKNKtj2j5CYt0Uglo7pXEOMs6KnvTXYEPS31lFOWWidsM4ZxkopKLCCcKXuI4BeyGc72vwAZV6MLs1gHLJGnI+jwnnImi3BRDYD6NcfaAQtm8pRJflQwgjYj8KeAAXIa/gSqoKTEiDtFqA9V9AYsHe5Kf0hax5sss21XpXrF8ZJ+tt8MpKoMk6ZixaqNh8KPK4Hf20BhmB1h8RmbKcX1rWBAxS1SGDVH1MoUuRqXErCrXzmQuHOiPchUMRrfo0gtQsb4i0CaL1Lhd1HYumJ8Ol/eRjyaCOyDDlDhwFVdKljMOcECxvKuJnouhcOXMz4847tXrbDPA6mAPMp0BWnxuy41Rxyrj0fpzF5r2RvW1OeT/O3h/Z2yafH4esNrt99rZJ1/2QvT9nb5udnZ8Gb7e4Pg0eIR4ZG0M3JgJ5bjczPA7AEDZSLpOSAdjG3zvWViyq+hkJRMCKqheHGp22HkpxffV0ZKrIXCHPRLaHHC8Lyr9EenRE3gpZNjKXtc4KObaQmKCjIM9AtotsVRQsXUos6MHPkOcjx0WmK0TAT6cZA3410d5ZIhu0l8gBbXvc/mnQ3nJFF6KZg0yz7shCtlW3f0SW1c3x0qy7ri0xfdPvxgBAIPpXFvD305P4zn5GIol3xbcY4xE5EEAouNV6avEG+zWQS/TOqQ4U4+98yiFFgcyVQiAuKS8kURmNovSaE50evCjXE6l28GEiqyiO62Mpl0/V4FY0zT22vacdz1yr50qa9ZHJTLs9XfelsIPm5Cuw82x0kYd9w310ZAPfsSCZg1vXcAVfgX82FswDHccStSeO1m4FDYUyENqvediO8r2uCFMBL1vVXgKtPFz36jdEhnosnArmJgkP07ZFU+jShqZmXfCQtarXotVVdceOKvt22tGETzaDwBI00a2J/9e+Ij4THfpYy4FvLJULtxkNf6RvjJOmkdF+uFMf5c1TH34s6qJ4HutjMIq/D8qc5vGC7XhKc9I7Irwe6p3vntd22SmYc3ZCv1TvHaCMnwKNiDOyWI85BSkpzIR8y8W7dyIGcbVyMv3AArhHCZrF9UHwRqytCgMC1y/5lj1Ur/GbG8BNk3t1dXZ7NMavJGgXDKm935Dw7+n9kbqu9xKO5v0L \ No newline at end of file diff --git a/assets/drawio/172.factorial-trailing-zeroes.drawio b/assets/drawio/172.factorial-trailing-zeroes.drawio new file mode 100644 index 0000000..2252116 --- /dev/null +++ b/assets/drawio/172.factorial-trailing-zeroes.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/assets/drawio/198.house-robber.drawio b/assets/drawio/198.house-robber.drawio new file mode 100644 index 0000000..0d7f5c6 --- /dev/null +++ b/assets/drawio/198.house-robber.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/assets/drawio/200.number-of-islands.drawio b/assets/drawio/200.number-of-islands.drawio new file mode 100644 index 0000000..adc0d37 --- /dev/null +++ b/assets/drawio/200.number-of-islands.drawio @@ -0,0 +1 @@ +3ZpPb9sgGMY/jY+LbLDz51inyTqpmzb1MO00EUNsNGw8TJq0n364xkkcHHXV0kGcQ4MfMDa/5yXwinpwnu8+ClRmnzkmzAM+3nnw1gMgCINAfdXKU6NMZ7ARUkGxbnQQHugz0aKv1Q3FpOo0lJwzScuumPCiIInsaEgIvu02W3PWfWqJUmIIDwlipvqdYpnpUYDJQb8jNM3aJwfjWVOTo7axHkmVIcy3RxJceHAuOJdNKd/NCavhtVya+5ZnavcvJkgh/+aG35NvaxKS4C4MwCS+J9h//vEBNL08IrbpDriSTy2CbUYleShRUl9vlc0ejDOZM3UVqCKqygb8mu6Iela8pozNOePi5Xa4niYkSZReScF/kaOa1TQKI1/V6JcgQpLd2dEFe2Yq2AjPiRRPqom+AYQas46zoMW+PbgWaSk7MqzVkI6TdN/zAaUqaJpvIAsHQ3bmGNlwKGShazEbDYasazE7Nsj6/0z2ApxC1yJwMpQINNajiWWy08GQPZ3btsnO3Jzbxupim1O7pb/+EDSWF+toAzdj0FhfrIMabMIDbC/dwWAzHvtozZTHiel9usTYB2VmMG6Aci6iriQhsQ/KzEicAGX8/FtfWc0Eww1Qp1PPOqgryResg2qxOAfKtYgCV7L7tw/K3P17UQx834tU2f+yyVdEqAJfqz+fKoYKXBkgFQ9p7EALXpCT7aqWEKNpoS4TBU11DuMaKU0Qu9EVOcW47jvuc0jwTYFrP27rvW0qEKaqn9On8oOktsKTxc144Wtdn6iBC22NgxkYRR1T9yYfmQrDyHR1/G6umomHYVrJaY1/8ajGWemQ3x/I1WwwqrI956N50WszQyvCvvKKSsp7vb0/abDiUvK8x3zJy74YUa9W1m+e79L6WHWUVwkiI0xKQRIkCR6VvFItf76ccJppku/H8yXoiYBzkXKJdTbqxsXUDAvwX+e6mTNVEgl5bj6/5vk7T+1XnFouofpcauvYdSoynQp7nAJvd0pdHs63X+qO/ksALv4A \ No newline at end of file diff --git a/assets/drawio/208.implement-trie-prefix-tree.drawio b/assets/drawio/208.implement-trie-prefix-tree.drawio new file mode 100644 index 0000000..84d2a95 --- /dev/null +++ b/assets/drawio/208.implement-trie-prefix-tree.drawio @@ -0,0 +1 @@ +7Ztdj5s4FIZ/jS93hDGfl5Ah24tdtVIq9bJyg0NQAVNwmsz++rXBJIDJJFUTiBoy0gSOD8a8D9jn2ASgRXr4u8D59l8akgToWngA6BXoOkSuw7+E5a22OIZRG6IiDqXTybCK/yPSqEnrLg5J2XFklCYszrvGNc0ysmYdGy4Kuu+6bWjSPWuOI6IYVmucqNYvcci28ip0+2T/QOJo25wZWm5dkuLGWV5JucUh3bdMKABoUVDK6q30sCCJEK/RpT5ueab02LCCZOyaA+zP+x9W+hl9hMaHVbRcad8/5n/JWn7iZCcvWDaWvTUK8Fq42HzH329jRlY5XouSPefNbVuWJnwP8k1c5jWBTXwg/KS+rJsUjBzONhoepeD3EKEpYcUbd5EH6I5UT94+ptzdn1hY0rRtYWhsWNKPjhWfBOIbUqNf0EtX9MIPJhjsCgbRxIohRbH8sRSDZlcxXZtYMePhFdN6itkTK2YqiiUPppjVVQxN3Y9ZimLkwRTr9WOGPrFi9uWR8jjCa1yDEJdbIUa10xKrZAX9ThY0oQW3ZDQTAif4G0k+0TJmMc24ec1VIrzcFxrGPBr5p+fwjTJG05aDl8SRKGBUwMFy71gPb1ouWpkeIhGnvaTlGpOXkOQFWWNGwpecltzzaxUycf9NnCRNG4GONM1fLDktv6AMyyYg/UYjVu/ZgAP9rz5A2rwXaXcmfSfSvXEDDowbo5Ju+pgZ9a1RQ6MXIhhTo1Yj92dHfTPWvQAaaebErNWc49lZ3wu1OzVqNVmKyy+0CLkNIE/MFhU7NbTlCrBeDNun0IeVxmGYnAuGC7rLwuMdtKEZawFZLg1Pt6RdznBB+1ZDarebtVUex3hqHCBqLvbMQAx9ciBqqtcDssFJ+ScT6fVZcCAUOc6SjYNEzSWfCwlsuqnjlNvAUzIuEufZkbj9GarJkVyRh18MuVpghqOvOsRrlnb0gfArITj7muI8j7PoJd+V21Pg1cNwDlcY88hNtmxPSnYbYobdJWZdOdKgewHTr8imZ2DtgWhyYlcs/83EWuMUnJzYFdMYM7EWMXcg2BuX2BWTETOxVuAx/TimzinMxN4hZqDJiak5LlSQqaF6d3qPC2dVH3VKbWOKvxsG9/AyxPpzG2JWbzF1YN3FGOCl342XmgCrj9jM6xQpDrwuMC4wNT1GM7DzwPSBtxXGBaYmz8YM7B1gAwsZowJDavJszsDOA0NTj2FIzZ2tGdh5YENR4rjA5tT5XWD9t3PMgTeHxw3r0VDqbCVMSgPEW/+NOtaPHa2FQK4darbdNllR9R04wF8C1wWBAXwHeB4IbOAFwHsFgQk8bjSqIh94SDh7EHgOCCzgLYGzrDYWwsidnUC4cYvjAj+oDl9WFhP4r8BFlY8LnKpmHwJHr5wXwLEr5wC4njiF41VFtvjvu2KD2x1DNlU426INjt9cOBeyvnZ5TZd7nFsvSNx44QFB8+Jth5r1ot/sKPju6fcWVVnrVyso+B8= \ No newline at end of file diff --git a/assets/drawio/209.minimum-size-subarray-sum.drawio b/assets/drawio/209.minimum-size-subarray-sum.drawio new file mode 100644 index 0000000..778536b --- /dev/null +++ b/assets/drawio/209.minimum-size-subarray-sum.drawio @@ -0,0 +1 @@ +7ZpBb5swGIZ/DcdF2AYDxzZttsMqTcqh2mlywAFUwBScJemvnymGBOyknZYQouWU8NkY87yfzWuMgabp5mtB8uiJBTQxoBlsDPRgQAiQ54qfKrKtIw4GdSAs4kBW2gXm8RuVQVNGV3FAy05FzljC47wb9FmWUZ93YqQo2LpbbcmS7lVzElIlMPdJokaf44BHddSFzi7+jcZh1FwZYK8uSUlTWd5JGZGArfdC6NFA04IxXv9LN1OaVPAaLvV5swOlbccKmvHPnOA7L7Nis9g+/bRfnk3ivr3O11/kbfwmyUresOws3zYECrbKAlo1Agx0v45iTuc58avStdBcxCKeJrK45AV7oVOWsOL9bOQ5D6bjiBJ5IVpwujl4B6DlIhKKspTyYiuqyBNcSVKmkicP1ztdAJaxaE8TR8aITIWwbXhHS/yRwP4CHlDgHaNnfkzvBIy8LiNgqpBsDSP7XIygwghdnBGwRgYJKZDA5SGNLZOsEY42OLZMshVI1uUhjS2T8AjnJDS2THIVSI4CSdwuP/b8z1gmat4v4yTphUgSh5k49AUeKuL3FbxY2K47WZDGQVBdRou+K86SZVwaR2jL4z0HMpthE+LTqGSbPZWArahkaVSC51LJU1Ryj6TyPxm4gJRR28oJWPbcHHBVlO0qYBg7Zyow8bXABMjp0Gzp7ptjGw1JU3XHDeDrw4kcTXKag+JUjbQ6H4+UJuzNmpajSU7dtHk+mqpP8q6GZjOMJU0bq7k58DpYNVRXk5sIdHMTY01uDvsYUt/JJDQLq87cqWZ+HBaso8oHfsw076czeBr1cFe89glzKTsGVNfcaqeuMf5v7UBv5CHzwl4aqGb6NvA+KZ6lWa4OKh5Uzftt5B0Sr3GzjXjehUceVNcKN/EO2T/kTrry2ZqNkGHlU9cmrXzwJt/Rl6FY89pjWPHUzQdFsnbrskPO/MCzL1zbsqtKCVnQ5AcrYx4zrX7fexUWjHOWagTmLNflgehfXnU13YTVLvQkLX1CJwHNC+oTToNJzkpR89f7hnAvt0RHl65PfV+TB0EsGpB9Ktmq0ucUOWD13vViB040azhPkwaWfa40ULdXDIgTLqkY1Q57Awy/rqpNa4ECEeyaC3c/hMPqN42z5nTRnbqFuuQ2H3TfKKNeLthnm8zF4e5rg/eyvW820OMf \ No newline at end of file diff --git a/assets/drawio/221.maximal-square.js.drawio b/assets/drawio/221.maximal-square.js.drawio new file mode 100644 index 0000000..8aa17ed --- /dev/null +++ b/assets/drawio/221.maximal-square.js.drawio @@ -0,0 +1 @@ +7ZxRb6s2FMc/TR5XBRwIeWy7tJO2K03qpLu3yQU3WAPMwGnSffqZBifBh9w1u4YDrH1psB0Cv/PH5/wxyYzcp/vHgubxFxGxZObOo/2M/DhzXWfh+upf1fJ2aAlW5NCwKXhUDzo1PPG/Wd04r1u3PGJlY6AUIpE8bzaGIstYKBtttCjErjnsRSTNT83phoGGp5AmsPUrj2Rcn4W7PLX/xPgm1p/s+KtDT0r14PpMyphGYnfWRNYzcl8IIQ+v0v09Syp4msvhfQ8Xeo8HVrBMfuQNZP3153X+eMteY775/TFwfotffqj38kqTbfOES/mmERRim0Ws2st8Ru52MZfsKadh1btTQVdtsUwTteWol6UsxJ/sXiSiUC2ZyNSwuxeeJGaTyGQdbcdX2/VxsEKy/cUTdI7YlN6YSJks3tQQ/Qa/Jq2lVm/uTnHz6qb4LGS6jdZK2Rx3fIKpXtQ8r2DrArbz0bI9Xo8DYUsmpFt3YLpdTEi3ZGC69abEdmC69Sc0J5i5zCHIcJcTEq6ZzNDhBraVW0HTHGcueXgg6m9YWQ4d+up/AN1Mf+jQ9fFMYpI2EyA+3SnbOQe7vnCgoRsvXZAD0elat3QDnI9BEkSnDs3e9KiDLIhOHdrA8c4kIAui052yEXTRa4wpO0F8utAKjpju4LRr3fMNKKuh09VKnYR2zayGT9e6t2sB2Qq8gyy2WGDThF7uO7XaI00za+HTtO/deqQ5OG1aX4DrkaaZlfBpWl9y65Pm4LRp3VthZqEVNk3rXgozC6HTvG4Zzbm2/oR3s/QTYV47eSvJyTMgewDyogXyojPI9pfNPijZSzcWu0ha2FLWZmOU0yxIWug00axTh5o1k5mHXRoQaKnGT9lMcviU0axWl5QHp+XrlsX+SynhhwF7fvloKZGzgqtTY0X1CTzbqB6ViTtJfm3wey0xiPXVMcTkhy/lKTk2D72UmJJjw6dpfbUL8b4hPk00a9ZFFsKmqeftUWoTZCF0mtAcAJi54JmqcNav6iRLXTbpL81VfCNaxkfY36qtlutbf10NSugzS34VJZdcZKovZNUnqI6KLg9p8osx4FlIKdKzAbcJ31QdUlTRpPXWcT/q+PLq8NP9pvr+401ahpTdRCwvWEgli25yUaqRf7x/FdHQwPmBRly9oT6GHSulJRksDBk48O6R26sMoHv5lMEFGZRiW8XIShkVGDpYwunA0ZfouRDczoRg/7nDPqtS0sC5JPCy6nd2HfMal+sMjeaYHZM7OG2O2TGRwWlzzI6JDE6bY3ZMIAsFyDS9MTsmkIXQaV63aDWS1eygCbnFlvZ6q9mDtvQ7If+rZM+5G0sq8/e/1njYmH211xoM/DE/NQhyGfp8geaoLqm4gxwXYFcMHtraVIeUzdyHT/k6BzbqadnMiQFZ3iA/4+VByzbd1W/X1D4+fvs/B4KXF/Hnkil5vAC7yvCn5PHwaUKPNyKag9PmdWZuWDTNO434NKE7G482QRZCpwkr2pl3V52Gp17Pv9A9T2m1l6e/tuosAWiFQl50BpArWKA01zFTHkXVvu+aETz7Iq2rt9vWKTcFjTg79VnMeNrH1pFT1TeInKuv9H5CB6vhz9B9JHS+jx46WEl/hq49dMtG6FYtT63aCp3aPP3o9Xvf2U+Hk/U/ \ No newline at end of file diff --git a/assets/drawio/232.implement-queue-using-stacks.drawio b/assets/drawio/232.implement-queue-using-stacks.drawio new file mode 100644 index 0000000..7a2b7c3 --- /dev/null +++ b/assets/drawio/232.implement-queue-using-stacks.drawio @@ -0,0 +1 @@ +5Zpdc6MgFIZ/jZedEfArl0k27W4nndndXPTaKlGnKFaxSfrrFyMkEtyZ7LYJTnvT6AsCPgfOwUMtNM+3d1VYpg80xsSCdry10DcLwgkI+N9W2HWCC+xOSKos7iRwFFbZGxairNZkMa6VioxSwrJSFSNaFDhiihZWFd2o1daUqL2WYYI1YRWFRFcfs5ilnRpA/6h/x1mSyp6BN+lK8lBWFm9Sp2FMNz0JLSw0ryhl3VW+nWPSspNcuudu/1J6GFiFC3bOA7u3aQOXnr98jF9yr7l/qvHyRrTyGpJGvDAUo2U7iaCiTRHjthXbQrNNmjG8KsOoLd1wm3MtZTnhd4Bf6qOSXeCK4W1PEqO8wzTHrNrxKqIU+ah7REwZ6Lvd/aZnAAE17bF3hBYKkyeHlo9U+IUA8w+QoAYJjQ4SAoYhIQ2SMz5IrmFIjgbJHR+kiWFIQF9v5qcStE+ckmcbpqQvOPNe6ZQSsk1T0lec+QCnUXJMU3I1SmB8lALTlDyNUs3C6FkjxV+QqThqVtFnPKeEVlwpaMFrztYZISdSSLKk4LcRx4a5PmtxZXw3OhUFeRbHbTeD/FULfYQJJF/p9AaWszNgAngxE/iaCVJMytXnNoMD0MjMEHy1leCpC8EZ8NjXtcDkKy6EwxZnLGaQDffMYDxuOrY6V9F5YRN4F4OkJxlGAEn1qL5xSPpHjwU90q7bOHtVYHkvTZszmq1pwW7qfcZsyitwIts9HFnOrxLxu2+nLsPifQ25M4ig5fJH7R95SXDeGgDavxrMxwztps6KhP/uvVAtu+U4up7V0XB5/2JSHaXPasnI5i3+pbyYegupi1wl/CDfBnx1Sh4SpX3fdl3npm+2PnmUR5OTMO+aji/6RusLhHnXHVmYR3qYH8H3sfdfcf5yCeAxniWcQjozzl8Okh7mR5C1O4EEBkLPdSmN8TBBozQQHK5LSc9tmvdK2uEdMn0upec2jUNykTqVrvr58XO1q5fACx7i+9un34uAvcLFwDnwOw+m+rsMvnNeBxGOIm1LwkueArfdZkmuGsQB1Ocf0sCLLVF+ezy635f1/v8BLf4A \ No newline at end of file diff --git a/assets/drawio/236.lowest-common-ancestor-of-a-binary-tree.drawio b/assets/drawio/236.lowest-common-ancestor-of-a-binary-tree.drawio new file mode 100644 index 0000000..e4d663a --- /dev/null +++ b/assets/drawio/236.lowest-common-ancestor-of-a-binary-tree.drawio @@ -0,0 +1 @@ +7VnLctowFP0alngsyS8tgYRmOm2amSyarjKKLUCNbXmECNCvr4xlbNmhEEIwnQkLrHv1PvdwdC16aJSsvgiSzb7ziMY9aEerHrrqQQgQDtQj96wLj++BwjEVLNKNKsc9+0O109beBYvo3GgoOY8ly0xnyNOUhtLwESH40mw24bE5a0amtOW4D0nc9v5kkZwV3gD6lf+GsumsnBl4uKhJSNlY72Q+IxFf1lzouodGgnNZlJLViMY5eCUuRb/xjtrtwgRN5SEdvJuH9KsLb4eY/fj9+Hz7zFZJHxajvJB4oTeM9GrluoRADaPQVsZwOWOS3mckzGuWKuDKN5NJrCygimSeFSGYsBVVsw714FRIutq5arDFQpGI8oRKsVZNdAcUaPg0f1xtLqtglK5ZLQ6lj+jwT7cDVwipggbpDYChFmDuhQEGXAMwgDpGLGghBi4LMcezLwsx4LQgayOWRoNc3pSV8pSaKKmNi/WDMuzS+FU3rlaGtdZWMQeNWoLYAFIpMBFTKvfFvA34gYAKGhPJXsxlvIaynuGOM7XAKp7QM+KJGyPM+UKEVHeqC2ZzHN8cBziNgBcwtAbaxHy763fQwD8VDcDH0KAAcv/pspcuqEu6IN/8+QfH0gXukJFz0aUttG+ky4rJrWio8q+av+JKbqzrxNl0sXy3I46hAzkGcKckg8AKsI98EDiu53sNZfGxhdW6gRt4CDgY4+MoCLFJQWifmYJ4PwVVGpzlRZZsMu86AfNTnKnUexCzaap8kmc17zfyROM7PmeS8bz2iUvJE9UgziuGJHyeCr5IoxGPudjMhSabj2qymWxQ5g72a4mEXs/VTMr81WKQgwPHYZQii6mXiwlLIyqsUM0IxxGRRD1y/1w9acj7E0WPXO/GLsi/b4lcCPqYxSSVj1JQ2gcwsLJ0eoJ8BeBGhhe085XgFSoHH5WvlMJ3gnzF8jxUF5K+bdnI36MmG+uOCqa2Q8Xps5ld8ThTOtM4n4CPjjygUEMdyjCdSR3KrX+qw0eqgwOQGeXO1aF9x/AZ9pOH3XUu7VBo35Qcl46W5f3p6AmzyuDQowF2mlVi/K+s0nZOklUiLzDHdfzznhsHXId8Csh7BQRi3zIlBDpdS4h7qrzSuADZGme4AHEO1JFub0D+cxlRZvU3StG8+jMKXf8F \ No newline at end of file diff --git a/assets/drawio/238.product-of-array-except-self.drawio b/assets/drawio/238.product-of-array-except-self.drawio new file mode 100644 index 0000000..3d7642a --- /dev/null +++ b/assets/drawio/238.product-of-array-except-self.drawio @@ -0,0 +1 @@ +7Ztdb5swFIZ/jW8mrcI2EHK5pGS72LSPTpp2NRFwAhpgBk6T7NfPBpMAplOqkeBqVK0Cx8aY9znY59gpwMvk8Db3svADDUgMkBEcAL4HCEE8d/iHsBwry8yGlWGbR4GsdDY8RL+JNBrSuosCUrQqMkpjFmVto0/TlPisZfPynO7b1TY0bt8187ZEMTz4Xqxav0UBCyurg2Zn+zsSbcP6ztCeVyWJV1eWT1KEXkD3DRN2AV7mlLLqKDksSSzEq3Wprls9UXrqWE5SdskFfv76+8eN+SXE6dcjXn8+wsh6LVt59OJd+4ELdqwlyOkuDYhoxQB4sQ8jRh4yzxelew6d20KWxPwM8kPZHskZOTzZUXh6fO43hCaE5Udepb7AlIoda03l+f4MwJKmsKF9bfMk8u2p5bMq/EAK8wyRsCISGl+kuWYimYpIeHSRkG6eZCkimeOLpJsn2YpIikYZjVJGcveRP2MhpTiNr0K2wCvCk4YNvQqW059kSWOac0tKU97gIvbWJP5Ei4hFNOVmn4jGeYHQNeJzwftOhTVljCaNCm/iaCsKGBV8PHl2aod3LRM9Tw5bMUveJYXvkbuAZDnxPUaCu4wWvOaPcsLi9TdRHNd9BAgbxmK5QgONG5bVom2psNEtYc8m2E/ADiJ+gezDnhRsGP7YaPPHPW/7TR3A0TACMSHWa0ic6yiSrZlI9f3bKhmv+N/48Zqlm0vB3tC/Vqv8HD+As7TzMaThm9hNmPom9duq9BIypvFVegkp0/gqvYScaXyV1KSpHs0BsmMRPG4ofzaxUFaHmvavHa2iSrzZmB6ymyZ7Kz4Rb8D2EiFgui6yskRMC2XDZt0y73HVeHXR6HS64dv4dNQsB/4jGQmgnqc1BWHrBqI32zgFPQMA0RNEN/ocH0RvRtONPi8EgvlPPxCoLRDd3gykpgPpLikUofgjswtWWZqLGtKkLJR011OSKAjEbXrlbwMSLFtrJvO5UdvlHhIyhiHldFZHkaWAMntAoauBUmPrCVQPKGSPDUoN7y8bztbWzFCHs5wUlw5m/x16bOv2kqpJy8T+NuzHf+8H3b+Cf4PZ3C7Qel9DutgAuJGh1/4VGnQDa6Ldoa3ZbiVS88eJ9mC0u3uTo9NWk9SJdkX7OpvT3S8njL45jdUtxckDrukB3el9fA9Q10UmD7iqB2g3BiDVA1wLOPzXAa4JFgvguMLyhh+bwJ0DBwLnvqzjAsdW3EWPFK3lk428DNpq/jZbGcZqNdAkD9t8oaPyhfiWCRtWV9RKeHZJsQF4YYCFOwF+JmDTuB5gfnr+An1Z1vg3BOz+AQ== \ No newline at end of file diff --git a/assets/drawio/239.sliding-window-maximum.drawio b/assets/drawio/239.sliding-window-maximum.drawio new file mode 100644 index 0000000..50821c4 --- /dev/null +++ b/assets/drawio/239.sliding-window-maximum.drawio @@ -0,0 +1 @@ +7Zpdb5swFIZ/DZetwOYrl0nWbJq2qVI3deqdC05ABUzBWdL9+tnFJIFDpk1JaqviKnD8gf28x+QcYwvP8+3HipTJVxbTzEJ2vLXwBwshByFX/EjLS2PxQ7sxrKo0VpX2hrv0N1XGtto6jWndqcgZy3hado0RKwoa8Y6NVBXbdKstWdZ9aklWFBjuIpJB630a86SxhijY2z/RdJW0T3b8SVOSk7aymkmdkJhtDkz4xsLzijHeXOXbOc0kvJZL025xpHQ3sIoW/F8a3N6y7z9KEno/H54+0+nDnK6nV7KB7OYXydZqxmq0/KVFULF1EVPZi23h2SZJOb0rSSRLN0J0YUt4nok7R1zWvGJPdM4yVr22xouFbyNflCzTLBuyqwHQitPt0ak5O2DC0yjLKa9eRJW2QestysmQr+43e8kCZUoO1MITZSTKS1a7rvcgxYVi+R9cHYDVOY3rOSihLqUJhOQNQPIuxQi6HtbPKDCLEQaMrvQ7EjLMkTwISb8nIcM8yQeQPO2MsGGOFBj4RsKG+VEIGPnaGbmG+dEEMAr0MzLMj9qQzegQyfF0U4KBpP5XUp/SLlnTRgmGkgaESf1YEgXo2tMMCsaTJ7pTL6tbhhGNoqE88DH0XM++kAMaQNaFLqh/pfYjdezqXqkDsbp5K1U/Jhitv4d1qp8rjPBPzIKM4OraurmePSswgquHdHM9eyZhBtfQ1cu1Hc97ew8M7Hu/LVcj05XANEowCo/ps7jskxJz5n/7vFKwgvZ8T5lIlq4KcRsJRlTYZ5JgGpFsqgryNI7lYwb5dxVasoIfOPDUD+1ZqOzqg50I684ild9VyvFhVO8OKIUuphQMV0elhpTa/d9rUwpGzKNSg0qFoWalYAw+KjWkFHYnmpWCUf2o1JBSboA0KwXzhFGpQaXaDXVdSmGYeYxKDSnlYZgjvq1SMJcZlRpUaqI5SscIKGUhP+Nq9pY8HdiC8Z/X8sDdTOboy9cTaAcmfyV/K1q3zcVwmh6aklH6/gcFP4C59NtqD3NpoFKL9gt5pNktq1OeMon4kXHO8q5+fRk4K3eqtgdAnSH92JpnaSGUb8+hSmViUic7mQZr1Akp5TDz7UoeoL0mmxrH168nV7/dw30l257NF0g+vy6bTpbpVj4BiH3MKcCG1K7LM7hHf6vFxzDZ8HzoHu0KPr97wM9yp7wainU+vhuOie95vd0b+2L/C+J2f3D6tezg+Dm++QM= \ No newline at end of file diff --git a/assets/drawio/263.ugly-number.drawio b/assets/drawio/263.ugly-number.drawio new file mode 100644 index 0000000..a744f51 --- /dev/null +++ b/assets/drawio/263.ugly-number.drawio @@ -0,0 +1 @@ +5Zhdb5swFIZ/DZepwAaSXuazvWinbW213VUOOGDFYGachu7X7xhMAqHVNi0brK3UYF5/P+fYHNvC86S4kiSLb0VIuYXssLDwwkLo0pnArxaeK8H13UqIJAsryTkKd+w7NaJt1B0Lad4qqITgimVtMRBpSgPV0oiUYt8uthG83WtGItoR7gLCu+oXFqq4UidofNSvKYviumfHv6xyElIXNjPJYxKKfUPCSwvPpRCqSiXFnHLNruZS1Vu9knsYmKSp+pUK19PpvUcWN2s0eXB3t1vnkxyNUNXKE+E7M+HDcNVzzUCKXRpS3Yxt4dk+ZoreZSTQuXswOmixSji8OZA0DVKpaPHqSJ3D/MFvqEioks9QpK7gelUV4zMONmPaHy3gGSluwK81YmweHVo+YoGEIfMblHCHkvltYYIJqzaLXEmxpXPBhQQlFSmUnG0Y5ycS4SxK4TUAQBT0mcbHwAmnJiNhYai7eRF+2zxn4I9sv82/tkeDv/sCf/S3+LtdL+3dSZFvD8tJvQFCwkOD5Hcgeb1DcocGadyB9PXtbnbYGQ9rs5u8J/qu4w2L/mX3U38KHwK5TCdZUsaOTSucwlQia6g3ZE35R5EzxYTOXQulRAIFuM6YkWAblYBrk1kIb8o/KFJ2Ns2zKsbV9En9smGFNsnMjGcRK6WD46kGgVZBmOILBuHxhoHp5EUAPaJVSBSBh9ZzeG44USMS6HHlo1LUIFaeo2nes2D7eEvkduSgyUWWRtqzRKoawxwvp/7yTC7heQPbDuujSPPL2v+n9TRIPiyT3jA53aXznqJkcNx+ty7nhcNc7256Gib376bdw1ztuQMKlPvH1D1OvOEw5DQI7H8td2Pw/z8OcX4ahywgyrjiJM8fP9O9kFtoD60g6ijgH1ImQtEBC+M04CKn/z4e6X9pdk8IFvK5Xokhe2r5iP9tp68XSzyjvLxcBXPYMP2ihFPnQyoyz7KdPCPpnzXkzZCPLQ+q2g8R1zg/7JI1LHXTA8y86qTdsXYJPYdaHeSG85qzad3cYKNzBRkeajkf9rrOhzA+y84Er8fb6DKvcaWPlz8A \ No newline at end of file diff --git a/assets/drawio/29.divide-two-integers.drawio b/assets/drawio/29.divide-two-integers.drawio new file mode 100644 index 0000000..d5338cd --- /dev/null +++ b/assets/drawio/29.divide-two-integers.drawio @@ -0,0 +1 @@ +7Zpdb5s6GMc/jS8XAebFXAIlPZPOdI7UStOuJgoOsQaYA26Snk8/m5gEsNt1W9KiJTeJefyC/fz89xsGMCp3t01Srz/RDBfAMrIdgDfAskzoI/4nLE97C/Lh3pA3JJOJjoY78j+WRkNaH0mG21FCRmnBSD02prSqcMpGtqRp6HacbEWL8VvrJMeK4S5NCtX6mWRsLVtheUf7X5jk6/7NpuvvY8qkTyxb0q6TjG4HJhgDGDWUsn2o3EW4EM7r/bLPt3wm9lCxBlfsNRms5Da4/fQ5Ltf/BPe7p6jxvtx/EBlEMZukeJQtlrVlT70LGvpYZViUYgIYbteE4bs6SUXslkPntjUrCxm9ohWTFE1bPke0oE1XFgwdD/o33N6yhn7Dg5jARUaIREzvJ1GcrB1uGN49227z4E3eDTEtMWueeBKZAckeKDugaUse2yNO15G29QCl0ydMZBfKD0UfvcwD0tE/4XRT8Xnf2wde5w1mY9eOXVbRCgv/kqKYmJKC5BV/TLmDMLeHwn2Ed+lARpQky8RrtCyPtI0xTss4DQ/LNRbOCAlSidgaINa5eKgagJeDw/bnhgOqQ1K8BGEE0PJysEDPmRkWWx21LgeHhaY43p2Ho/BQp+4/lge0Z8fDvehZxJgdD0/hYV8QD3d2PJA6rU9x1JQIb8Yb3sq2B9JvB4RnsqRdH9z2I2pF8oCLf2lLGKFaVH9PEjxQxmipYclorUPOq1aLmpe7XGx+F2WbJniR4brBacJwtqhpy1N+7fahk27EdzyGEUZL60zzla/wt1T+lm7bcy7+vmZZ5wDk8WWdCWIXBBEITNjZliBYgtgGYQgCpwv4oN/GzVm9L+54T0HVnq7VTV+lakLnDWVtqrvZq67PpmvbhPNStak5QOplDa+yft1i1pkcUb2/qDV78KuotaLOCC9A1mmLW3aqLjHWOTSdd9a5uv0/6Ny66vyVi7Lp9A2RivWNla4eI1yV/pZKt5WDvvfXunqUcdC6fdX6Ly3UZqB09UDkqvQfK72ijeB1ij5hKofItqM5lNGdypzvW6RuW+4CPxIijxEIbRCgzoI6pXdRyBfaDwMhf2HhYQPEnhgEUDdA+MtuNHBFyjDuApEYP3iuIAZ+IBLzX5GYjyGG+Lgz7Ypj5f7sV+cx6CzBaJUq3ZTHuCnCD6sTTe/QHsF1+gPHAdzDfYghXN89E9x+LHlJ8klb7+9NrMhOuHvo1ho3hNcEvzQcPz88kLK7WzGCYx3tN6TMeasK8iDaJpTL/z9WK/rVWrSb/ERMzOmSy9FITvv5/2xfm3V7K7dg3bizGcFx/3sUt0M6D35oOxcGPIFl17vOPX08D+XyvyunrZPq9wpyQssHDs9p3JANyTga435L+e9Hjj3HTdu/irtg/7ZxDbi5a0xvneVyYHTTZDgHaNYGXhy48cluQIzXBh7UHOIgzdrgF2YB/ni8YtTFDS5qwfg7 \ No newline at end of file diff --git a/assets/drawio/295.find-median-from-data-stream.drawio b/assets/drawio/295.find-median-from-data-stream.drawio new file mode 100644 index 0000000..beb310a --- /dev/null +++ b/assets/drawio/295.find-median-from-data-stream.drawio @@ -0,0 +1 @@ +7Vpdb5swFP01eWyEvyg8LllZpa3rtmraY+SAG6wBRuA2tL9+ppgkYKplUymWkqfAxdjmnHOPryEztEyrTwXN4xsRsWQGnaiaoY8zCAHyPfVTR56aiIdxE9gUPNKN9oE7/sx00NHRBx6xstNQCpFInneDocgyFspOjBaF2Hab3YukO2pON8wI3IU0MaO/eCRj/RTwch+/ZnwTtyMD12+upLRtrJ+kjGkktgchdDVDy0II2Ryl1ZIlNXgtLs19wStXdxMrWCaPueHi9nb9nP6I13JVruTPz/Cr9/0CNb080uRBP/ANrVTgmtFcT1s+tVhIVqmRFrFMExUA6rCUhfjNliIRhYpkIlMtF/c8SXohmvBNpk5DNVem4otHVkiuUP6gL6Q8iuphFtuYS3aX07Aec6s0pWKFeMgiVj+GU3cvMql1Alx1rp9AdciqV6EBO8CVUplImSyeVJP2Bh82t2iRQjgnTWC75xy0kowP+HZ1jGqZbXZ975lQB5qMfyCGmMTw7OSIIcixjRhwaTCDDEJUP8qgXoPtgCda5o1r3fOqBvJNQHNJBzRvCDQ8gBkeCzPoGpgBuzADXldoAE8NmSkzaBdkSmi2ycwzMDMg263CtWNFtIx39vU390zomiXfRMklF4OW+aXXYC2kFOmAp0qRD1mvmlpezzKtNnUtNU/LkLJ5xPKChVSyaJ6LUrVcvZQ1PTufQeQ4i2UAez5M9Hmn3WUQBC/uLameKoJvpAnQ1QQApiTggCQQGUsSviEJbFca9Ze4yZ2nnc85i6bKIkIsyyJkbhPamsuWNEJOFzPsgamXI4TNJdw21EjXfDDwJ8bM3PeczeddzQfhniTIUCK9r/2YtTCxK4+g232BQND07mMWw65doCFEeqDhiSEzi0VgmdCQj2xb5vC5Xpx61+X3EsnxpjVsDM+S+E9JqFL7bYzC6xkFdqdexvHALsKQhX2vsgeYCwI0HlHwuBIcjubnA9uWM02o95oG+8cuvOMRZe6VzC8RJ0cUhsg6oszPH+ZLyJMjyqha8HHl/3g0nT8fTF619FyWOOZi+L41i7klNHeEJ5e6yO3RNF7qqtP9X2Rerh380Qhd/QE= \ No newline at end of file diff --git a/assets/drawio/31.next-permutation.drawio b/assets/drawio/31.next-permutation.drawio new file mode 100644 index 0000000..edb2eff --- /dev/null +++ b/assets/drawio/31.next-permutation.drawio @@ -0,0 +1 @@ +7ZtLk5s4EIB/yx58zBToAfg4j0xySWqqpmofRxlkQwWQI+SxnV+/0iBsi3ay2RocaSpzstUIIX3danUjMcO3ze6DZOvykyh4PUNRsZvhuxlCMZ5n+sdI9r0kI6QXrGRV2EpHwWP1jVthZKWbquCdU1EJUatq7Qpz0bY8V46MSSm2brWlqN2nrtmKA8Fjzmoo/asqVGlHgdKj/COvVuXw5DiZ91caNlS2I+lKVojtiQi/n+FbKYTq/zW7W14beAOX/r7771w9dEzyVv3MDffb62839HP65/7L3UO3WH29zz69s608sXpjB4xtb9V+QCDFpi24aSWa4ZttWSn+uGa5ubrVSteyUjW1LsX6r22PS8V33+1ofBi+thsuGq7kXlexN+BB93vXFLZH/tSKyhP0g4xZja8ODR+h6D+Wy/9ghAAj6p9REhYjDBjFL2O0rOr6VtRCPt+LC8azZa7lnZLiCz+5kuQZXyynoUoCszwCqBLvlkcCszwKGCHvjGhgdhS/BjePSeKZ0mtw9P4pTe7qL+C4/VN6Da7bP6XJnXfBuvK57lSenFIHGUrSK+oZWhKgM0dzl1NGE++c0hDdOQmPUza1Q596EuKRcSVz4h3afGr/PjU0Eh60wbmH6+7HlhbA9EQw3vLv7nHkRhI0888JRlwBuHsaHicYc037qmaZ5Tw/+6pmkVFCo4ncW4AWCAOzwNYEOoJGkP81AUZpga0JhIYHDYZsgBlvi2uz/6FLec26rspdTHr0cv+3QXqVkKH8jy6/i66ig+BuZ6H3pf1p6YHLSo+HSyvsu8ALsKEyIo0ixeSKq/+MFKBKfhK45DVT1ZPbj3NasE94EJXu4Ume7EYBKTlofGikExuZc3vfUZugKYzcpuYINNXDAE09G8Zh6C+wFRip+o8fxuniPPU+p3AEOAUQP5DwOMF3yYGliyRAaDDzCSw0GKeLIUCDmU9gocE4XQwBGgzv203TAW560MqF44brrWj5KLa3IlZXq9ZEFRqSWftvDMIqZ/W1vdBURWEec1Ybrr6mVggaxWoIaoOc0Qa6mDZgdLus2sLcZoKEP16DWpaiVfZcDops+SSpi6Ik0TP1IqE2JfjMfIrPTSiCL6VCGGtLzqRkrY5zjR65mV3RgpfsoNg3pf5gCyVJU6DSs7Myo5dSKQyJu60mZmflttLd0klKqfWbsMaQbBfd+plGJE3/GtGZiittCEYtKDLaetO6O5VHWo99T2QCA1et39roqKie9N+Veh56L+rWrB3LDK6ZOWo4EEu+bkRfAaecJTw6Fdl76Y2OmKmuHH02BoEinTY3G6XzU9EODevx9G27z9Pic93QYqfDb3bnZJbjozcZPHxzeH3tGN7FTnHB4N9/Bj4+fZN5PgpAYLAfQPqdBAYpyFMloVnSpTc4ftFZ1NBsD+5uvDAbnwDS+KCld0gw3/uNs+84ioE6fmn6TWDu9kJ/6cUVgA8iUu97nQSmUOEt1wFwGhoOKvYDnzkEwAkmZ/5XF/CpQwCcYC7h//Q1+NwhAE4wnfidF+J5fEYhEy3Funj8iLHftT5+Corf/ws= \ No newline at end of file diff --git a/assets/drawio/32.longest-valid-parentheses.drawio b/assets/drawio/32.longest-valid-parentheses.drawio new file mode 100644 index 0000000..1fb47b6 --- /dev/null +++ b/assets/drawio/32.longest-valid-parentheses.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/assets/drawio/33.search-in-rotated-sorted-array.drawio b/assets/drawio/33.search-in-rotated-sorted-array.drawio new file mode 100644 index 0000000..463024e --- /dev/null +++ b/assets/drawio/33.search-in-rotated-sorted-array.drawio @@ -0,0 +1 @@ +7Vtdj6M2FP01fkwENubjETKw1WorrTSqOm8VA06ChuAUPJukv762YxLAzO5MmwSPlDwkcLGNOef4+vo6ALTY7L/U6Xb9O81JCaCV7wF6ABB62OHfwnA4GvwAHQ2rusiPJvtseCz+IcpoKetrkZOmV5BRWrJi2zdmtKpIxnq2tK7prl9sScv+XbfpimiGxywtdeufRc7W6imgd7b/RorVur2z7QbHK5u0LayepFmnOd11TCgGaFFTyo5Hm/2ClAK7FpdjveSNq6eO1aRi76nwPfn68uQ/fcu+RtV65y6f/vjy90y18iMtX9UDu6q37NBCUNPXKieiFQugaLcuGHncppm4uuOcc9uabUp+ZvND1R6pGdm/2VH79PhcNoRuCKsPvEhbwVWIHVpM1fnuTABWpnUH+9aWKspXp5bPqPADBcwHQIIaSN7kIJ0GiCkgIQ0kf3qQTFOSo4FkTw4SMk1JWAMJ/j+QlkVZLmhJa1kXLf2MZBm3N6ymL6Rz5dnHDrYuBKtp2nM1WNHk2nNM056ngeRMD5JpSvI1kPDkIGHTlNTe/3KhVZ42a1nWPvmuNiSFV4q83KlB1J3WZ5wLHAjnuIcswl5rmQxb3dcZMCF4JiKlO7zpZwVspKYCE6cGEzUF9emBpfWK8PuG3Kzriz8/60PTd1kVrcjAvylTWharip9mHC/C7ZFAs8jSMlQXNkWei9uMctFnqzcHLWnFOh4zSRD/KLvK5MALeVDs9xm07TEGgxEGEb4Wg0bmThzPQK1DDanpEyh2YCJSRmZRjNSUiakUaKSm9HwKiF0QLIAfgBiDKAR+AuIA+AiEvrD4PvB1V2boBKQmGr5S0CYkS34uFJhqg6AT1HeotR08MgddjVt9fSS59UBo3bl991SgBYhGcKuvzzTetrQQsMc/+JM2LXPtPlMPUusd9JbpMym/06ZgBR3l9NugwDNljG5GSGd0O6YN3rWt6PlmvxKbhfNNk6VknpNtTbKUkXy+pQ0v+ZfcuNMX65YVLRJ4W21gNBz3HSff0Qa8pUtHeugJYg8EDohsEDty2PMfPty5l1fjPgERArEPohhEwbsmgJhX4WUWsp1IehQXhA8glC1H3Me4/QZ/5XWO3QgT2UXeIJadjkDkiOp+LC2+LPwg21mIFu6+qhdiaHrErjumR3dEkMHVBKlH+A1fzrI7eYP86sCVOHp+1RnhDV6Nt7H4YehIoByLlhzTylS9bhqAoyPHmFfjt7d0PwHHXJAjPEbo3WPQ/7DnP5AP0uVz43E/EqOMT0R80hCc9/XDMRfqEVZfVHpLZ6TKf6IylTcTdQIQJHfNXXa+6W8JOd7kmtOz8QjNG5LW2XpWVLOaMhFLzhpaix8eS6aHT0aqzJoOSPXi0I0vNQ/5wwWPj/FIEIHGJiPbuhqz+u6BGtvQLQVdzzU/WjEJgm759BSfEuiXWLdALRVlj61pbxpuOPqmx/Qpc4iHSLl4LKK+7R/F9BXe9ClzZJmIlL70mD5ljozUlL65MH3K3DFSU/rmwj2t+nHHqkUZHrQnT6s6I9shQ97aPGWxkW8HdAkcz3T+MkEqU6tRmr2sJDfdfyDJDy8ibxY22+NbDIKDtD1ZFntBYKT687BmTLz+EAokYJLlFZoXGa2WBWe9nmf8jjDJU5byH2Fv+O+yJmSG8tmqpE1zmMm08ZKLZiYKzBoRYyUul33yQErCyHxbrYbS0f9ydkMxofZPOK2URrNenqMrqbV9QEn89PxehrzWebkFxf8C \ No newline at end of file diff --git a/assets/drawio/334.increasing-triplet-subsequence.drawio b/assets/drawio/334.increasing-triplet-subsequence.drawio new file mode 100644 index 0000000..96874f8 --- /dev/null +++ b/assets/drawio/334.increasing-triplet-subsequence.drawio @@ -0,0 +1 @@ +1Zphb6MgGMc/jS/XWFBrX7aubrlsyXK75JJ7szCllSuKh2zt7tMfWGyrdNkuc8UtS9QHBP3x8PB/qA6M8u0VR2V2y1JMHeCmWwdeOgCM4TSUB2V52VkmwXhnWHGS6koHwz35i7XR1dYnkuKqVVEwRgUp28aEFQVORMuGOGebdrUlo+1eS7TChuE+QdS0/iSpyHbWEEwO9mtMVlnT8ziY7kpy1FTWb1JlKGWbIxNcODDijIndWb6NMFXwGi67++JXSvcPxnEh3nPD5u76z292fQPEt5/w3otcEP260K08I/rUfuFKvDQIOHsqUqxacR0432RE4PsSJap0Iwdd2jKRU3k1lqe6PcwF3r76oOP960u/wSzHgr/IKnun0cS0y0z15ebA39em7Ah9Y0N6xFf7hg9Q5Inm8h+MgMEI2GcUDosRNBhB64zAwPzIMxh59hkNzI98g5FvnREcmB8FA2TUjdnexDKkyQAnWzdoW4cUfoGobR3S1IBkf/nvhm3rkBq5Pigh2Q3c9imZcttZBM40cuR4Lnxn7jphbGCTDctsB7+NDFXlLgVakq3C3AfDIGgzHAcmQ3iCIfw0hqYcdxYTZxap/4XnzGdOGA0Vpu+3YALXN2B6J2B6nwbT1O1tmKETTgcLs+2ZwDdhnnd2mwLffgwM3M789W3HQFPi219Pu5ROTczzUjJFvn1pZlA6sRacl5Kp8g1IVYZKdUryeqfvGImCQRJEZ5SsCmkTrDyy3qBHTO9YRQRhqvSRCcFyWYGqgjlK1qt6ACJGGa/7gsv6T1apO5s1Qc89FQH181xmQqitzJkiAeIkLeCIJKxYEjm0fJTIHkGcIoHkQdkreVxSJC5Qop6ruqiNSqTGvloa4x8kWT/cIr6WK2U4KotVDwPvTUBHBIyNgZe9mSO/N/Y/9GbuMngpNenIUetSykxtvo6UCtpSyrctpYCZAX0dKdUR+b5tKQXMwP7BPa4UVVldt6e10GukczOZbesqYAbED+549Y4sGBoyM/59UGT1jcwfmpdBM8p9UL33jmxoXgaH+BtrMDjHAuby2YVUMlIIzBfP8iUrzWL/q7p77EtuG1glOFvjRqgXrMCNjD/S9wlWjb8nATidNyB9tW+nyTzy7Up9GzHKqwThUYpLjhMkcDoqWSVrPtSfKcj6S0LpUTLhuvMoBuqtiLxBP0PBuBqeXsIxDNsu4JousJ/dZ5FQ8MRu1JdP58ZvpnOXMlm7oqiqHr7jDeNr2R6IZea0rbOnWCd6Ku8jFCeUVbhO686wQE9NGdhbmicvD9++1GVHXxDBxT8= \ No newline at end of file diff --git a/assets/drawio/342.power-of-four.drawio b/assets/drawio/342.power-of-four.drawio new file mode 100644 index 0000000..6e8e0c8 --- /dev/null +++ b/assets/drawio/342.power-of-four.drawio @@ -0,0 +1 @@ +7Vpdb5swFP01PFbCNjjhsUnTTZOqTerDtL5MDJwEleCUkIb21+8SbAIYJNYGjJZQKYXjjwvnXl8fGwwy36RfYne7fuA+Cw1s+qlB7gyMHTSF3wx4ywGLWjmwigM/h9AJeAzemQBNge4Dn+0qFRPOwyTYVkGPRxHzkgrmxjE/VKsteVi1unVXTAEePTdU0Z+Bn6xzdIonJ/wrC1ZraRlRJy/ZuLKyeJLd2vX5oQSRhUHmMedJfrZJ5yzMuJO85O3uW0qLG4tZlHRp8Pye/n5n7Lt98+vJf3Benr457EZ459UN9wXx8n6TN0lCwlIwMVsnmxAABKe7JObPbM5DHgMS8QhqzpZBGNYgNwxWEVx6cJMM8Nkri5MA6L0VBZvA9zMzs8M6SNjj1vUymweIJcBivo98lt2/CVfiVqEDlrZygApmISIZ37AkfoMqokHhDBGNVFweTq61BLQueVW2ckUwrYqOT3zDiaD8H+h3mui/JAcgk+r1gMwzdRdclBNsS7MTsOIES6Ef+oGk30ZSySvubpvPBMsgzWg7B2WoRpndLXFYvTFG1LCl46bM6TbS+6PMUiijI48yZGHNnNkKZwamYZYG/eC1whx92WdKZrbkUXKzO+q4W6gAj58eyZHlcLYS/4/97LZu9LmO7BkBnmxoav7gB8iv2ORL+Lnn+1hagafPDVWNA3x8DomOMuNnRAhhjOW1NGdgMlnc0sW5ZoZJNQCxo+a5AhtmaqAN8/NIHXUGB9BaBphoFqho0iSPLod/hHTr08YV2iV5gOoWp+oiDY9LNtgfW9X2phrk7YxXztcZ67oM7Y8ypFA2HTlltmZtKkdhgzbNFFJnTdmqTP/EEjHbjppBr1Blpz7JckngUM2gkgjN25eNtRT2kPfj3Fe69yRINbzIpCG80JB5H6tL7LGtFzuRNuiYVNV6Zb1YHWCfG6YmavnrZVAeu24dlAOtJs8wVEuLSYTVxaRIPueZMRynEp0WdvQqOXKdMcYShj0kP6tJ9g46YxB1h7GLuzPneV5jVLmbjLr8dzj/nnnTqu88Y9UDwdSdZxp2TUU0XB1VdlTHtyj9Oapdrgw2IcCBLjF746o4sIn27K1udY5sCd6JsyHlPlE3J4eX++iqzT+izafVUJpONKdidZdVRyq+SCFdSytUvnPQlopl5F2F9LD6rD6/UM1JwVJ3xa9CusFR1GwYsYM6qn1n5fqdyP/4nci09saxx+9E4PL0kfSxrPSlOVn8BQ== \ No newline at end of file diff --git a/assets/drawio/371.sum-of-two-integers.drawio b/assets/drawio/371.sum-of-two-integers.drawio new file mode 100644 index 0000000..4241c20 --- /dev/null +++ b/assets/drawio/371.sum-of-two-integers.drawio @@ -0,0 +1 @@ +7ZxLk5pAFIV/DcupApqHLtXoJItk41SSLSUtUAHaQYxOfn0apVW6nWRSMd5jdBYMXN5fH/EcabDYqNg8VtEi/ShinluuHW8s9s5y3R5z5LApvLQFz9sVkiqLdyXnUJhmP3hbtNvqKov5srNgLUReZ4tucSbKks/qTi2qKrHuLjYXeXeviyjhRmE6i3Kz+iWL67Q9Czc81N/zLEnVnp2gv5tTRGrh9kyWaRSL9VGJjS02qoSod2PFZsTzhp3isltv8src/YFVvKzfssJnPykmT/bz4/PTwFt8+vqQDh4f2q18j/IV7x5t/aIQVGJVxrzZim2x4TrNaj5dRLNm7lq2uayldZHLKUeOttvjVc03rx6osz99KRsuCl5XL3IRtYI6BiWZdnJ94O+3pfQIvapFbYsn+w0foMiRlssfMHIRGYVYjBggIxdMRx4iIzAd+YCMGJiOAkRGYDoKDUYOOSMPTEc9REZgOuobjJjBSJ5t3QWxrCvxjY9ELipZKUUplxzOszzXSlGeJaWcnEk6XNaHDbtMms9BO6PI4rjZzUny3bY5A3y/y77vG+y9E+zdf8VeRQCoC51uTp0+sUIdSAsfolFCNPG6QaWnBGnj4bSEaOR1k0pPCdLKw2kJ0czrRpWeEqSdh9PSWwz9/2pWXZvarZpRwfLHoPznoqzb39pdNa12Z7lsMmHy70yXXFv7mPjmx+SiDaU2DHXJ1WPFnhrZj97XECvoKV1DrKCndA2xgp7SNcQKekrXECvoKZmxAsAww2nJjBX0WtJjBT0lM1a4BiUMW3sG/FqsYA5xrGCmW7XcIG9gx9n3TiMEz6umL8nWzD8st+5+IBeQZ7/ZslHz5VjS/t9uZ7mIyr/bkD9koWP5clV7uirkUMzl4Gkt5PCDbNaEV0u1N0lht8PuQcjy9nxUFVJfv4lN4XgQjM+kQ6fnd4S4v3geCdENLqpEyESgfaf4tkd7tWSIiUDPTfSUIBMBnJYQE4Gem+gpQSYCOC0h3mjQcxM9JcgbDXBauoaeQ/SUbrnvkK9uh1DlJrUzqOud7lYDh1ijHqSnD9EoIXp63a3SU4L09HBaQvT0ululpwTp6eG0hOjpdbdKTwnS08Np6ZY7DwUutVs90XnIDaKiOfndELQtCDsS+aH5kblooykRHTda23T3hjpqqP1tCrKGgkw52vdkeOIGz2UfvURMOXoWpKcEmXLgtISYcvQsSE8JMuXAaQkx5ehZkJ4SZF8mOC1dw50LekpmGrmdLBgy4h5fyi3fY8WvY0XQc4kbyowV+9De9mnrTt1bsNuCvRNdWy/bgpCRR39NiGdekC77vhnEyGO8AIucEmTkgdMSYuQxXoJFTgky8sBpCTHyGC/CIqcEGXngtGRGHnotGS/DoqakjucmHwXqUd/GCE/mjfujQIix6ayPAvW7QuyHphDP9SiQnDy8kXg77+i1zmz8Ew== \ No newline at end of file diff --git a/assets/drawio/378.kth-smallest-element-in-a-sorted-matrix.drawio b/assets/drawio/378.kth-smallest-element-in-a-sorted-matrix.drawio new file mode 100644 index 0000000..1d79d59 --- /dev/null +++ b/assets/drawio/378.kth-smallest-element-in-a-sorted-matrix.drawio @@ -0,0 +1 @@ +7VxNk6M2EP01Pu4USHz5OOPYm6pkt1LxIXtLaUC2qQDygvwx+fURNthAaxJPGVudNb4YWkLAe93idQt7RCfp/nPO1qsvIuLJiFjRfkR/GhFi03GgvkrL29ESOM7RsMzjqOp0Nszjv3lltCrrJo540eoohUhkvG4bQ5FlPJQtG8tzsWt3W4ikfdY1W3JgmIcsgdY/4kiuqrsg/tn+M4+Xq/rMtjc+tqSs7lzdSbFikdg1THQ6opNcCHncSvcTnpTg1bgcj5u903q6sJxn8pIDtnL5O1vG32f2N7n+Ot+Sr+Tbp2qULUs27Rsu5FsNQS42WcTLUawRfdmtYsnnaxaWrTtFurKtZJqoPVttVuPxXPL9uxdqn25f+Q0XKZf5m+pSHUDcykUql6HOk3s07M4MuBWqqwb4tY1VnC9PQ59hURsVMh9AiQCUXOMoUQsbShSgNL4OpUWcJBORiPxwLF0EIQ9DZS9kLv7ijZbXwHVcqydc0XmfA2PUug7YGwRpYBwmF8Jkfi7rRql5mDwIEzUPEzpv8iFMxDhM3aCzzU9OAUp3stDhNIY4IdAQ+PypfrShUqTjcQsmMjYPExTu5t3JdvDhBKX7laK0D5wQ+hMU7xhEJkKH0qjxKycoI2kOxjlNo+ARaAmEQGk0vHlxShEGq0bFm/coByFQGhlvXk84CEMP6ngA01rEmeT5dKtus6jQOBV/S+QiVqxOMDYga0/4mcjUgC8Je+XJb6KIZSwyZQ55ObhqKKGNQ5b82unwKqQUaaPDcxIvywYpSopYtXcaR13aurzydL8sS/hPaREy/hTxdc5DJnn0tBaF6vnnoZoOH1eW9TKZkZ4IJ23CHR3fRMO3TW9W+u09HzHzuPdIC1lqvsJCMKYw1MaHE8YUhiL0J5wpTAcox3yJhfSfwtwg8jAAhTMhQehRKBMSEHoI5iiUCQkIPQRA9Z+QmFlNxueDFOqJQrJcAnTVXcsLEpQmrpUJ5BjdVCSNo6g8jZazNqsLkcnqtSDbq/YbZM1mVH36ql97NTkVXa5HNXQ5GrrIzeiCsoZn0UAW8SBZrnGyoLRS6A1kORbGyNLIu/8BVQ6kyjp8+poE7Q5VHjFPlUZgQjnwcFzBOdAjY+NcQY0bDFQ5lo8wrKDKhqWlh6PKdkiXKl+n2u9L1UUvED0cV4oawJXxGbA+2b+XQx6OK8cK8MWVA1chRsRLSmaieKs2l/Jw90dTsWZZbcuE/JxzphiYKERl3UVdRbNXw9waD70z3DgjcElXZiLICBxYGhniVkMVAuniwLKIN1Clocr8FOte9JOD/+JKIeWFAX9ddAhT9ojxYBHemLVehGVXrPjatZv7sqNZkhiEpUZY+vU7JsaY0qyJOANTMLPGEFUwXft4EaS5FvXe6hXyKQ8+kBCQ48H8DHDzoC/xRbE6oLqGQmxKenpxA+8yNzi9x3cfP9D8+v1BhUm3juwHxtfS6pXz4XHXESbdKoofEONcad69+3gk/XBcwYqXr33/475cwTXqQZpgCSQfBtIgTW4sTbqrPXo3uK808WGQtsrSDX/wvm/KvxA6hMin4hAjz6qDwmB/QKhu19eyrxnIfaF+MHLVodYvCiZizVOWJLyQanOa8LRkQN1neRZWtgpFU6Q2vjCZx/sfrGBO6v2G7/rTZ2/a16qk7Xb8NHA1BYm6Ltr0Uu/jXqp2z/9MdWhr/L8Xnf4D \ No newline at end of file diff --git a/assets/drawio/416.partition-equal-subset-sum.drawio b/assets/drawio/416.partition-equal-subset-sum.drawio new file mode 100644 index 0000000..6dea7e6 --- /dev/null +++ b/assets/drawio/416.partition-equal-subset-sum.drawio @@ -0,0 +1 @@ +7ZtRb6s2FMc/DY+JwICBx5Am27RNmtaHPV654BDfAmbgtuk+/WxiE4ipmnsXgqPdShX44GD4/Y+PD+HEctfF4acaVfvfaYpzC9jpwXIfLAAcNwr5Rljej5bQ846GrCap7HQyPJJ/sDTa0vpCUtwMOjJKc0aqoTGhZYkTNrChuqZvw247mg9HrVCGNcNjgnLd+hdJ2V7eBQhO9p8xyfZqZAdGxyMFUp3lnTR7lNK3nsndWO66ppQd94rDGucCnuJy/Nz2g6PdhdW4ZJd8IPN+2SRfn4p3mz67BP5Z/LpaLORZXlH+Im9YXix7VwT4dVdilxQtqnjPipw3Hb77imtGOKtVTrKS2xitetbf0BPO/6ANYYSKo0+UMVrwDrk4EKPkOavpS5muaU7rdix31/7xLu1gq6Y6SmpzC1KNHTngVHXh7T1jwhdW4s7BNklLsCTcG3akTHG9TPiIYJsihvhG2Buxpc0iWKiWz7ltmz2tKlJmX55QtnBAuKzKTLLht4MPH0J3Oin5HMC0wKx+513UBzypvnR/YMv228mZAPSPtn3PkTojkg6cdec+acx3pMzfIDnQJHc0zflp+AQTar/tCcOPFUrEkTc+x4ceoKlyDWbRkJkLdGYKax+Zsl2dWPBjkkw6SQA8E9zRBXfsEcU749UlDzXJfbMmCV+Chsx8qAeWEWRgKmJKM3ORdXFEIQtHYvFNkbk/Asu0gSXyzyaJrynujyjuT6a4f2+Lrx968y6+Drw3ZBDOnK84esJiWCh2fNOQ6Qu+4V4WeHMji+7Ny2ZHBvQcyTHNzRzTmOlfUZiG7GxmhnM/r6o08n5m5vzI9FTc9Jk5PzPPdDcLTCN2SfrfPpUJAi2WT6hdARI4C19epFOCN6WkZ/z6c/GtKbm+aZT0JN+dnZLnmEZJz+vt2SkB1zRKl6Tyt6bkm0ZJXU+PEpyfkmnR29Xz9WB2StC06O3qKbo3OyUvMI2SnpVHs1MKjKOk5+HO/ItcaFz4Hkm+58++Q+Pit559h7NTCkzLK109r5wd0nnCBEe+urstJD2ttADM+bBxU6FyQAv+/SIKoeIdLdmiacvAVrwDv/9DS0cd53uZ2KaV5ceE91jwf8fyH3jzq9jIAcRLxXaMY/f/Jo64KFmaBmzZ7r03hbYXbLdXylTOXlL7UH//1oX3YS0EnEhG5R8jMqbkdQIVj83ypWi0o31928HvXN5grB5sTN5wqknq6ZmoRrMrXRR0UtTsW7L2kGLDavqMFbeSllgVG/SqEBLOCdeXlCmMVzcg2erOo+ojikMmClCXRZMgvExxVeMEMZwuK9rwnl/aWlChLcnznra2Ha+34HMf2G6hzZ/23LimDMlLdV17mmc4GOhlCmOFKZOVKXiXFab8z32iiws9nwBgIp/wo2hmn9CfMlQsFlwsUYKt0JzCvLvbtRNHi/zWxrPi0AofrE1k8eAWRr3IfjzhB6GdM2UXeFlfVGnSHOXcnwqSpvlH7ypOS4r9Db5xDVcIz8ID0FOCcMQVJqtb8/QnqZ6K9yhXF96vscTbn0fzsbdO08mlP9EN03A1I6EVrYWEcmracme1kjvxitX8DBvfim0rdobWC/PuO3GHhwheyR1Cb1iDCL1gObK8j+V83/ETAN48/aKkPdb7XY67+Rc= \ No newline at end of file diff --git a/assets/drawio/437.path-sum-iii.drawio b/assets/drawio/437.path-sum-iii.drawio new file mode 100644 index 0000000..9bebedb --- /dev/null +++ b/assets/drawio/437.path-sum-iii.drawio @@ -0,0 +1 @@ +7ZxNl6I4FIZ/jcvykARCWFofPb3pOXWmFtPdOwZSyhkUG2Or8+s7CAGSWEhR6tU+s6kiNyGQl4ck9yY4Ig/z7R95uJx9yWKejrATb0fkcYRxgJj8Wxh2pcFDTmmY5klcmlBjeEn+45VRFVsnMV9pBUWWpSJZ6sYoWyx4JDRbmOfZRi/2mqX6VZfhlFuGlyhMbevfSSxmpZVhv7F/5sl0pq6MaFDmzENVuGrJahbG2aZlIk8j8pBnmSiP5tsHnhbaKV3K8z69kVvfWM4Xos8J6V+7++9x/MRnn54n8aPzZfnn5K6q5WeYrg3hV2KnNJD1SLll4n4zSwR/WYZRkbORD1zaZmKeyhSSh1VlPBd8++ZdorrtkhmezbnId7JIdQJR1694YVVy04jvVaZZS3dlC6vHPa0rbhSRB5Uo7xAIWwJ5w/V5TdL0IUuzfH8eeWURjyJpX4k8+5e3cv5hniubdBJFsasriiiwpMSSVJmgmGPsuhRyLYU+IBAIdMjXoashhJLUsyTFV9XPgQtE7ZEAgSrkMkMhD1gh31YIFiHjHSMEWCB2+92Wc2WSBvZYCdtv1fOfa1FI9aMtiWyBFvGk8AVkKkrD1SqJdF34NhFf5bFTHX9r2R+3rYzHnUos5L1/VcWKxLd2TnPSPqXOGso0jy0nxXhesrnZOo94l1BlORHmUy6OzXjt59/z+eY8DUXyU7/dQw+9usJzlsiG1HgF+vvnUgObspXVSW1nx6gHGZNeFxkVlTJYFe0RrFv9ASptx+oCVILyBcoNdrE+WXDoMHCwo/sCVsd1bnBsh/N3B8ft2TExUMA8X+cCkYGAIQMws4s7N2C2+z0YMNQbMABcAkhcEDNwCQbigtxgTImPKWEuRchzjOmX6459OQdDAWZBELj+ZVmyAxUXYKmee9WJPnOvU490PQj0QAkMdAKJM5RAr5tAD5RAO65zW71Z31k5AR38HIOloYMfot0sUVCW7BDYRXuzMabv6dD2qWeeJ7LdPD89mV5PMn1QMpFBJh5Kpt9Npg9Kph16hBtnxxgYTNITTAoKJjbAJEPBZN1gMlAw7ZAv9Pox0fXxSDVHBYtP2iHcm1tBprqmFAFriu2YL/ASsqtmRFcjkR2AvLnFmMDALoDWFFuaHhiHATs7eIXs4BLwKrI5XvoOtER2zAR4Gdl4zXwGrZDt08N27ghfm0K2p/qRZWGYSYVS52pEPaGT9XsvJFeT/qMOGIINwSP9rQ3UlOz9QSu9IkaNis7sZGHbyboAmLCIgZKDqe6706G7EDDRvYJ68nEpcmzf8wLknJCDesv/0U0roGstmGEDGCMM0xsY1wDG7LPODAyxHevBwPQPNMJuW+mNGGw8EemI+Wakr3+g2+2KJ/oBA4wnkhNum7qhBWUVIzzOIAZlEBsMekMZZJ0MMgeUQQzB4Clp6js/b32GBkETMWgaOmiioJsmBEoTyHarq11UVq0/zqYLyqaxndT3h07onG42MSib17R9C3xZWS0qHUcTdi+OZ6DJhqKJutEkoGge2tdFU1F4C9m+FQ2j9Mc6Uxl3q/0X5BNZQFKwbTJHxSfiypFojMTnIeVO20SnxX+X+ONlKGZ3q/X8LkkSdXXZmvIGymLW+yL4VuhviO7FLLIFN1yeyhSmyXRRvGaS3ILv+yK8mkRhOqky5kkcp2+Fc/NsvYh5rL0WJ119cw98oFh/nX2Z74YP7c/6n4kLMoHNPsI/HxMy2fw2QtmtND8wQZ5+AQ== \ No newline at end of file diff --git a/assets/drawio/454.4-sum-ii.drawio b/assets/drawio/454.4-sum-ii.drawio new file mode 100644 index 0000000..87d3e98 --- /dev/null +++ b/assets/drawio/454.4-sum-ii.drawio @@ -0,0 +1 @@ +7Vxbc6s2EP41fowHSVzMY3xrH047mclM2zxlOEaxaTGiICd2f30lI4xhFZucYINPyEMGLbp+30raXQkPyGS9/SXx4tVvzKfhABv+dkCmA4xdNBL/pWCXCUwbZYJlEviZ6EjwGPxHldBQ0k3g07SUkTMW8iAuCxcsiuiCl2RekrC3crYXFpZbjb0lBYLHhRdC6Z+Bz1eZdISdQv4rDZarvGVku9mbtZdnViNJV57P3o5EZDYgk4Qxnj2ttxMaSuxyXLJy83feHjqW0IjXKXD3NKMPf/+7cMnv0z+Ws+mz9e35DllZNa9euFEjVr3luxyChG0in8pajAEZv60CTh9jbyHfvgnOhWzF16FIIfH4wiKuWMSGSk9YyJJ9XcSZG8Zc9HCsmqUJp9t3B4QOMAn1omxNebITWfICRCGrVAu7eKiG81ZQhW0lWx3TZKqinlKP5aH2AkHxoED8CKDmzwOoY7SOJgATfQ7NBlDC6KBmCqcRxMkyIEzWpVDCAKVP6lwTKNldQ4kAlO46ABNQJmTpVrGrIgWXsLsOTDugUB1ACu6eXUAK6BR23baRsm9ijeoAUA4AymgfKKBRxDHbBmp0ExrVAaByV+oIqXuAlBgzL8OR8oT9Q3NTM2IRldZnEIYVkRcGy0gkFwIjKuRjiWAgPKh79WId+L5sRot/maH3jNuqEdwAVUe7h6LKhTyZGp7wxXiC1u645wnyhGxHM6WuSxU0uSc9VZAqgnSr33Wpgnb/tKdKQ9XIaZkoaCKe2M9RpyInJi6DaWriUJYucpLvOc2jCe3IdLPusOKfYGo+tw1sN8SUU2bKMVvWemjGLgQ2vGfKGnWLKQzNaOiZdY4lZEKWjP3fZeYTstumqU7M+MvRVJ1MCLkt0wQtaLjd9zTZdtv2M4b28w2ZZZUTwjzGUjrR0qGJL2WXYRhc7+2y/T5Socq2W9Z7GNvvDTO1SHWNKmhD91s+oInk8cXWaHJ7mmrQZI3a3vIJ9HR62wzsUMTFLdMEPR2zp6lKk0lajmsS6On0yx5c9lqfTdDP6WkCNJlEd0HiukTVuLJKI/9eXv4t8Nc7oCfhExAlu78U5vvEk0yI8avkdHv8crrLU9uA74sNsaWST6pp+VyUkom8UKZH+RVjAjRLdGyGptbMOfBKfXBz+SyrNa8gJDT0ePBarl5HpGrhgQXSMToswdW5TSpVpGyTLKgqVagDrKgaXMxNyLwi7iVLykFFgntvd5QtlhnSEx02Kh4NKV2lFg9ZjYXSHjD9hB7rzrvIDaw5WDNpXGdqOE4za44DDvzbDlYSeJgmxnwDNvHVqSJmy7EAAmMBgKb9ckCT2SuVq4IiKv86QyLoe+nqAOc5NkPvOw0fWBrwgGkp/FbJ8J1xztYajjmLdaoguhbLnq+3S/mJzXCdLjw69Gmc0IXHqT+MWSpyPnvZjldSr/1+Np7M8Qf0wg9ExaqvEUskjU3oyuGCQT6rSd3ruxdTFhM6ur2ydFJZiFn3Yu7llKXOwaKoJ4jT99bpI+3w0jj7XO0l2ErtGZfPUMrmYPXY5BQh58zGZk97EdK5AwjSYp6wIj9HC3SvNXfwvxovI62bdlVeen+6xgEvNlqP9pq1Pvf56SdQxWU12p4+0OX4eqTk31IfjkY0d4uuSkoN56Lh0NMQSYSOw0+OadYNQB1Fn1TYqpH40761B5oEAlW59k6LxfF8UEqAtY/+1ND9LLpzdu9vLcxV2XTNwhj6eKCrEoAy3GpVDYW6iFtx2JGhWvrBYJdIFr9hkGUvfgiCzP4H \ No newline at end of file diff --git a/assets/drawio/48.rotate-image.drawio b/assets/drawio/48.rotate-image.drawio new file mode 100644 index 0000000..066d438 --- /dev/null +++ b/assets/drawio/48.rotate-image.drawio @@ -0,0 +1 @@ +7Vxdb+I4FP01PBYlMQnhkbYwI+2uVKkrDfu0chMDngkxctwC++vXTuwQfGnLzLJ1LLUvxTeO7Zx7j7kfJgN0t9l/4Xi7/oPlpBhEQb4foPtBFIVoksp/SnJoJOlo1AhWnOa601HwSP8hWhho6TPNSXXSUTBWCLo9FWasLEkmTmSYc7Y77bZkxemsW7wiQPCY4QJKv9FcrPVTROOj/Cuhq7WZOUwmzZUNNp31k1RrnLNdR4RmA3THGRPNp83+jhQKPINLc9/8lavtwjgpxSU3LHL0bbGf7dff0ddFdaj+jOe/3ehRXnDxfPrAlTgYCDh7LnOiRgkG6Ha3poI8bnGmru6k0qVsLTaFbIXy45KVQmsxTHT7jhWM12OhIEAonEq5npdwQfavPlDYwiTti7ANEfwgu5gbAo2sNq3QIL07KirWonVHR0aGtWms2pGP6MkPGsCfADMCYEb+gBn3DEwEwETegBn1zTJjAObIGzABzceOwUwAmLE/YNo0dw3mGICZeAMmoLlrMFMA5tgbMG2aR673zAkAM/UHzLhnYBp3voPmxBs0bZ67RxO67f4QPU565hyF0G/3xztKwr6hCR13f0LKpHe2OfL4Wwgw3bV/FMI4yB/XHTDdOZowEPIn3wGY7hxNGAn54yHZTHfvIcFQyJ+40ma6ezRhLORPMs5munM0zfwdNAGYW0ZLQfjsRT5kpTFrCwgK3xxX6xbsDrCV4OwHMVCWrJQD3hb4iRQPrKKCslKKM6IGlxcUsDTDxe9WhycmBNt0OkwLulIXBFOKxLrVjiOXtlUr3+xXqgw03FQZJsOcbDnJsCD5cMsq2fPvuiKj1E2L4kTdt3fz6CfMIqdyYL3WknGlxmuYCgqiYfzu10IUxNBaUPx/WYvPJRqLeih1TT0Y6vnjsoRh39D0uUYT9s42YajnURriFMwRcg2mz5GeTXT3aMJIzx9v2ia6ezRhpOdP9tYG0/muCQM9fxJkgOjO0YSBnj9JCEB012iajeatQI+U+bQJikyw9gvgkT0VCym7CYZBpNt/Kf0MJ7p1v9fqqhuHTuOBcCofV4VxtcwKzXJM0mUGAkt5JclS8rRsr5jTcVGrTJKDo3WWKiUS7Jln5N3NEur8Qp1yUmBBX07XcU7ReoYHFXdf8N1hhmjWr+86mgsYKHrNdzcDCcxXRICBartrH/s/mCKMIoEpfuYcXOQc2nRUa2Ip2LXSMxZu6mxX37VG0FT8+Q5A4djK4DiP60Yw5+CP84yS/uHpc9Zh1EP79LnEDPnuPLwb+Zx6gHx3j6fPZWbId/d4+px+OMN35/snzED4k7c9w3fneMIchD8FrzN8d41nDPH0h+9x+krc7OzHHzCn4w/bE9Q3NL0ubvfONmGk6Y8nbzPduZ+UwDjTHz/eZrp7NGGU6Y8XbzPdPZowxvQnRweY7nzf9Lm8DZjuHE0YX/qTnwNMd44mjC4BmFetIZq6YFM/jN+sH16jVigVww8Lvdi60ZlZNY9T1y0zd4PCVWqMJgPa1ODe6picN56PKUaaN4to04ztI6cX1yKtihOafGwtMoEBKTBpU9yjm/r9LF2DPl8efLeqWNcjb3H2Y1XvPR3jXNZ/sks92bTaNu+RUUaGTWNJ92q3utXruV8LoV5AM1XgRPMsL8MhzVi5pHJX48NMzhjNcyyw/KfklQIKC8IpLm4yxolsKwOYcyak+IYrY7kJo3S4LVcfu+NZxhCbw83OXghwwaGJT+v4sHxW3w7PjT8PMvT0IEMc2Kbi+CDDGKZDRulQc6qhqm058unFBSbR1YAWAa3ayt/QPFfTnHV2T93hjiKjACpyPJsms+A6SoO/eEnO/DwqQteheL2lmfedNe7E8a1xaPYv \ No newline at end of file diff --git a/assets/drawio/49.group-anagrams.drawio b/assets/drawio/49.group-anagrams.drawio new file mode 100644 index 0000000..be1a3e7 --- /dev/null +++ b/assets/drawio/49.group-anagrams.drawio @@ -0,0 +1 @@ +3ZnRkpowFIafhkt3gADKpVq105l2uvWi1ylkId1AKERFn74HCSBGZ9bWHbNcCX+SE/Ll58wJGmielKscZ/FXHhJm2GZYGuiTYdsW8ifwUyn7Wpk4Ti1EOQ1lp05Y0wORoinVDQ1J0esoOGeCZn0x4GlKAtHTcJ7zXb/bC2f9WTMcEUVYB5ip6k8ailiuwh53+mdCo7iZ2fL8uiXBTWe5kiLGId+dSGhhoHnOuaivknJOWAWv4VKPW15pbR8sJ6l4y4DVgca/v/zg7vPraDbbf3tm/PtIRtlitpELNmyPQbzZC4ewthdV11YjQvRTXS5M7BtaOd+kIakmNKF5F1NB1hkOqtYd+AO0WCQM7iw5w5wznh/HItP0PITaqFuSC1JeXavVEgTrEZ4Qke+hSzPAk9Cl69pN2HV72HSJT7bPlRqWronayB1YuJBsb+BsK5zN/4N3B0jtM+gCCekIaaIZJEdDSMjRDJKrQHp6eno4JsfUDJP3puxvf7Ts7+iW/ccavrOubtl/oiMk3bK/pRZsWKEE6xV9FIXI+StpXreUp6R6AyljZxJmNErhNgA+BPRZRY9CNTyVDQkNw2qai+z7u3MH/Nb4DL+v4ncu4LffDb9ax/0aLn7IyprhVyvEYMD4fd3wq7VnOFz8yNUNv1rVlsPF72iX+9VqeT9c/K52uV8tog/Dxe+dHxUfjl8tz5tjYUi3vV3w/mx4c4YcFccvulPoYHlZ2TV2h8g6SJHh9N+jBMBfFCfn1DpcfwqQj4965fSqh3fOjsXLJULHY3Gly6/jkIju4rHzBIvcB1vMv4vFjht+xWX3inOTWy8GcmeOb7gw0lyBATL4naY4ynFyq4s/rrfHi6m3MN/H2+ffttti7sTc6JK7vdvdDbfdvzjHtpP/wtDiLw== \ No newline at end of file diff --git a/assets/drawio/494.target-sum.drawio b/assets/drawio/494.target-sum.drawio new file mode 100644 index 0000000..98daac7 --- /dev/null +++ b/assets/drawio/494.target-sum.drawio @@ -0,0 +1 @@ +7Vxrb6M4FP01kbYjNQLbQPKxeXRfM6NI1Wh3Pq0IOAlawCw4Tbq/fu3wtt1NJg0xmelIneILGDjncLjX4A7gNNr/nLrJ5hPxcTgAhr8fwNkAgLE5Yv/zwEseQLaZB9Zp4OehRuAp+BcXQaOIbgMfZ60NKSEhDZJ20CNxjD3airlpSnbtzVYkbB81cddYCjx5bihH/wh8usmjI+DU8V9wsN6URzbtcb4mcsuNiyvJNq5Pdo0QnA/gNCWE5kvRfopDjl2JS77f4ytrqxNLcUxP2eH3TfDr53Qx/WrOfnuwPnoJflrcF708u+G2fcEZfSkhSMk29jHvxRjAyW4TUPyUuB5fu2Ocs9iGRiFrmWyx6A+nFO9fPVGzunwmG0wiTNMXtkm5Q8l9IRmraO5q/MvQpgF9GXMLxtdVxzUobKHA5RswAhJGQD9Gdr8wghJGUDtGoGc6QhJGSD9GPdORJWFkaccI9kxH9g14tuloBmnUQ9MWDUk7SOX59Mq2xdtNP0pykqTfuFHvUJLTJP3WbfUOJTlRGoCJhBPriRU4+DhGbpbkVc8q2HNcJysS06KKAhfLNa02iMiSQEQKEFFnIMqZ1H2HEK6CMJySkKSHjuFq5GHPY/GMpuRv3FizHFnIMroBHRi6QZdTs26VqwV2IR3WL3U52etS6pdHUL9unR9Bt6B3Hi0n4D0XrgihfuWOby9XgDbqlw6BXOJ8d7mCCLp25QK5YvoOPRfZds+0LpdgPfdcEUL9ylXVZ28pYruoxywhxRrLRa0SNbsz1OSCjLrpGlMWOzQNeViJXT9tQ9W+WWMSY+HOLkJuGKxj1vQYXpjFJxzNwHPDh2JFFPh++JrC2+x1QI8F2vTYsqRNoGAHdMaOXLnF2yi7EUZ4u2HgzvzBnl+qxm4nfIqxIce6Jk+9HNgXs+KxLOfrvo2V6zn9Y9aV3fYGJbn80j8aC0HfUJIrrIRkAQ2e8W264+PjbMxSqouw5bTJql7LHEnNOrNHKFdyb1P0FVIzONKdmkG5FHsjbB08VOBIsxFAuXLS/4pPfKjoR0lO82O8dt/tUmGXyNBtl6r3NXZICxQG/JPJEgj7ny3/CpHBAMeObzhOM2Sv898Tk1dzfBiHV3PFEjuIcc9+QPEbNSu+/Gjs7PMDlh1d0rM7JBQ5gkkB+fZTFgjdeblcIJxCqXH41ww14C+DvIP77IDsA2fPTvYKERz2OInVH+7+RyYYCoI5cTywOwtQvfo6TS+eJ7P/9OXTT4s7fqPzpc931a1ejvXkXS/TWhTX0kqIV/RmlCK+IkWKJNFC1xx5QHKSWJGd+3zNuGIc/ybu9Zk1H83QhYb4jhMIrjrEh+T8teZNQaPtRhzAeJklN2velyVUNO8ecHpeAveae/OuPrQU0fLuhkD44PDdu5ufO8vAUlRp13Xz89PE/33snyEYwWaMc0PvWjxLizbUnlmoUlCBJBz7D3ymXg3+6QUfF+10WvFaTtGDEtPNbfE+oH9ydobmqGh+5YcbmkVrti+4OzReGo0FTgOGDH+0zeq3UNiXJhEKlLELJtvUw0dvW5nbE0d7UhzmIzHNg6r4LI6wIMHBC8qxeWckPAFtNC4jZTf5NRR71sqQOrMM+1hXuY1IXR1EVl38G3R3yvyPm0hzKtVewB+gA9u82LI/XLVCRapP3ASWEq4RnM6f2UVmJU/lXFoOoO9mmwrNY2SG7hKHi8NLHqJk8KOwwZJQSiIFxZQkKiWwU0v4mUf7NZ8QPYwyz8VDHycp9lyK/WFCMrblX25ueeKnS4YxmT6Ca8tCnOCmGOlSfYVjdSSL0tPeZdErWTidyYI164nw+TOg/msCcP4f \ No newline at end of file diff --git a/assets/drawio/5.longest-palindromic-substring.drawio b/assets/drawio/5.longest-palindromic-substring.drawio new file mode 100644 index 0000000..4b0dd3e --- /dev/null +++ b/assets/drawio/5.longest-palindromic-substring.drawio @@ -0,0 +1 @@ +7Zpdb5swFIZ/TS5bBRsTetm0ZZvUSps6bZeTAw5YNZiB06T79TsG0+A40zY1gXRrLxrz2vjjOefYHJIJvso37ypaZncyYWKCpslmgq8nCAXIg/9aeGqF8AK3QlrxpJW8rXDPfzAjTo264gmrrYZKSqF4aYuxLAoWK0ujVSXXdrOlFPaoJU2ZI9zHVLjqV56ozKwCzbb6e8bTrBvZCy7ampx2jc1K6owmct2T8M0EX1VSqraUb66Y0Ow6Lu190S9qnydWsUL9yQ0SX9wWn5IP2SOq392xL+8FCc5ML49UrMyCF2a26qlDUMlVkTDdy3SC5+uMK3Zf0ljXrsHmoGUqF3DlQdH0xyrFNr+cqPe8fHAbJnOmqidoYm5AU0PMuIzXEVxvDUCMlPXYdxo1Jk+fe95SgYIB8xeQkAOJjg+JnBgkfIKehE/Nk/wT9CR8ap5EHEjJ6JD8U/OkwIFUUsGLpJI5jxtWFS/S0bnt7uV4DzcIUxdccCxws0PvU0suxJUUsmruxQlhYeKDDhaQD6xXE6IFDoLDcPX833MNhsQa/hNYfXxiWC9eZ5j7ZOww7/KHoznkMoxZHO9zyEVI9PqPEuf7wA7qkJ6bMbzwdB6F626gj8/VTTIcrJBBlrrI8yZp7UPUMDhkrZeCpwVoSpY99ZYumPgoa6641LULqZTMoYHQFXMaP6SNyfpmaP6gSTPYZV22ybXGT7uLJd9oI8/NfK4zpXRWfqlJoChOCnzOIS9fwn7FqvMYRkRRQhU88kZar+FzKag6o7GeV33WiHrLi4jGEX3m8cO3O1o9AJ3wvOz2uRcZnnhkZ4cnjuEHfZDzDp44jXJyotDmSoKxA8rNtd4CaoCA2mf4YQPKzR9fZUARm2swGzug3JTzLaAGCKh9hh82oP6NlBn/AddhA+rYOfMwj9KIhBbXWTgyV+Smfq8xRcHeqXF1U7//4gCoGKNlKWCWzdxQ1OzzEfI3CJ4xo2smmGIH2/xto/tk5PSks/Gb0Y9pdPtlxL5IH9bobk46QYGAYecJf7SMH3xf6S+w50tZqLO6+foeOE9h+ZtmG+zqoZSaz6afuqTFyzoiczIhcOP0VhYpqxWUPlrvRu9Xi+71aDsmsGiHtaeira9X1ak73g12VbZT29t+IQu2c0YYiRqnj8EPWLUnGnKeJHqYveePfUJpLObXEai77oXF7OYyuDnUO0cUWO6IfNcd/e6hsO+P3t/7I1xufwHR1PV+RoJvfgI= \ No newline at end of file diff --git a/assets/drawio/516.longest-palindromic-subsequence.drawio b/assets/drawio/516.longest-palindromic-subsequence.drawio new file mode 100644 index 0000000..de4ea46 --- /dev/null +++ b/assets/drawio/516.longest-palindromic-subsequence.drawio @@ -0,0 +1 @@ +7VpLl9ogFP41LmdOgDyX87Dtpqc946LtMgaMtBgs4qj99SUGzINMZzqNkvbUhQcugcD3ffcCVyfobrV/K9L18j3HhE2gh/cTdD+BMIRAfZeGQ2WIfb8y5ILiygRqw4z+INroaeuWYrJpPSg5Z5Ku28aMFwXJZMuWCsF37ccWnLXfuk5zYhlmWcps6yeK5VKvAka1/R2h+dK8GYRJ1bJKzcN6JZtlivmuYULTCboTnMuqtNrfEVZiZ3Cp+r15ovU0MUEK+ZIO+N37h4fF9MPN1wgDMPN+PGxnV3qUx5Rt9YLnerbyYCAQfFtgUo7iTdDtbkklma3TrGzdKc6VbSlXTNWAKurxiJBk/+REwWn5SjaEr4gUB/WI7gA9jZiWDDAI7moCAm1aNrA3tlRTnp9GrlFRBQ3Mb4AELZBS9yAFIwMJjVBJaGxK8keoJDQ2JQUWSNg5SP7YlBRaIK1TRgss+IpmR6wELXLnuHVjOerBTbmpDVx4LuCioePUgjJ2xxkXx74IByTGvrIrBvg30miJ4RyF4TC4Av95XMNLwhr/E7D6aGSwJn+nm/uBazc394ezCXIRZyTL+gQ5j4Ny/Wfx8z5gLypIYN8Y/nB3doJr19Hd4zr4+dlJAIVx0MI18hzjCu3L2wSGTL32FtNHVczLYgDCa8aLnGzkVSPAXm228w35viWFwlj3mgvTyVjUvBpDWaQpMGWbmTYDBS9Ihy5tUhPJC1XNFPhE2W9LamiWshvdsKIYl6/plUJbLAteSJ1dgabe0EA0vQmnQ8UsGLY0AH1bAz6IbBGAs4nAdq4JvIV/CVUDUBLAzrmmj5Iet4RnY8S+5PUfbDxGilxNcfRUAXieo1TQcwQFUXxJruy7pkUHKfBNmW2tYW7uRB1BKyTE4bOG8Fj5UlaukzAyhvt9s/n+0Kx9JIKqlZXcHY0V8SY7i040EGyldjskQE+mIify2XOkzdYL7/2CsFTSx/Y8+hjSb/jIqZrhk8e/wO+QvOFbkRHdCzaSv52BLFV5cXugCghroKNgTsv+Aw3Z1+6hNHTtgaipIzA+DT3h8ZfRkI/AQBoCHQ2BC2vIzjEMpqEkRp1YVBvGoyO3sShJ2vTHr9QRCBzryE6qDKQj8D8WPROLEjCMhqDXESNILqohZKdBfqWhjKWbDc36DqxNnvuPk8/z7opO2LlWBGiokNAd6Nx09mUJBqeT7Kn8bKKCKlc7TaBrdYgoKyZCvNz1K6x/FfcCp9tH1KY47t5XXqqVburu4lrpSyacTSueG60g32lcCdB1FHinj8nfGMa7v6q++hYUXVg69u///7MeFTN+1GbGJISdZT2QnaH697YEt26O4rhN+mtvpn7i2K/tDNkq3Y/fe4dLL7cDtB/Z3jtQellV679MVvzV/ztF058= \ No newline at end of file diff --git a/assets/drawio/54.spiral-matrix.drawio b/assets/drawio/54.spiral-matrix.drawio new file mode 100644 index 0000000..61f5ea2 --- /dev/null +++ b/assets/drawio/54.spiral-matrix.drawio @@ -0,0 +1 @@ +5Zhdb5swFIZ/DZeN+E64TNOm07RKkapq69XkggPWADPjNGS/fsfBhgBO0qZJJ61VpOLX9jE+zznGtuHMsuqOoSK5pxFODduMKsO5MWzbcm3bED8z2tTKxPRrIWYkko1a4YH8wVI0pboiES47DTmlKSdFVwxpnuOQdzTEGF13my1p2h21QDEeCA8hSofqdxLxRM7CHrf6F0ziRI1s+UFdkyHVWM6kTFBE1zuSc2s4M0Ypr5+yaoZT4Tzll7rffE9t82IM5/w1Hab3y/Dx7uu3JHn8udgs5uvV0/OVpPOC0pWcsGH7Kdi7XlIwC2/NN9IV/u8VVRVX5RbUFBq4blG1lfAUi/+WcTs3rmfGZAYKygqQ8uey2LYw7UOVzqFKV73dM1MjDRXvkAX/UOX4UOXkNWMHjQUI4Oax9YbV+BdA1S5WpuyOt22OK6EnPEtBsOCx5Iz+wjOaUgZKTnMsaJA07UkoJXEOxRQvhYUXzDiBgJ5KOSNRJAa5XieE44cChWLENaQvaIyu8giLiDGblxIGcLU36qwmlmERwDTDnG2giexgezL8Zf6r4rpNJseVWrKTSI5KGyQTOG5MtzEODzLM3xDyribke77vekHnp71YDNuZzx34gxoKfQgXE/dMPao4RWUphykwIzBDzMRQJI9Bts/EwO0ymGgYOBoG/qUQeGdGsOvG/Th0/mcoIuDFntwDdwYEntlFYI2HDMYaBE27szPwPx2Dfhro8sDWrUUXy4Pxp2egywNPg2ByKQSTT4egWeoVAk0aWDoGF0uD4DgDnEdTsZuHUig8TMKu22HubPND+H3kjQMlPIFwZY5M01HKTSXZ1KXNbmmhPsBKrAivTdqeLD7tVLWmREFZqt8cR4NTRQ8PzI6uWIiP7805YjHmx9oNcR/JKKUxnCJOXrqvq0MsR1hQsj0YNNHkdaKp/72qZyk77Z5O+nYmXTv97V/thYGdbbw1kz49BNVx8z0x+Fr0/wqV39uDQJafxmpgKPA+Fpb1/8NyrW4+OH4wOhGXYx41dWlgumuG/wxYs2KppHBOXAn7hqzgg5fC4QHZc0dlQRgSfTPEGakG+N5/XRECR/Htfc+Fxe7uq3/6pjmX14tWcKbN7LhLyrWGOynH1ISc+/adFBTbq8IadXvh6tz+BQ== \ No newline at end of file diff --git a/assets/drawio/560.subarray-sum-equals-k.drawio b/assets/drawio/560.subarray-sum-equals-k.drawio new file mode 100644 index 0000000..026d4cd --- /dev/null +++ b/assets/drawio/560.subarray-sum-equals-k.drawio @@ -0,0 +1 @@ +5Vtbc6M2FP41fowHISDwmE3SbR863TY73e5Th4BimAByhby2++srgrjp4NTrAIJsHjLoAAK+852r5BW+TQ8fmb+NfqUhSVamER5W+G5lmh5yxf9CcCwFNjJKwYbFYSlCjeAh/pdIYXXZLg5J3rmQU5rweNsVBjTLSMA7Mp8xuu9e9kST7lO3/oYAwUPgJ1D6JQ55VEpd87qR/0ziTVQ9GTleeSb1q4vll+SRH9J9S4TvV/iWUcrLo/RwS5ICuwqX8r6fTpytX4yRjJ9zw+ffHr2/P/8Sbn//au6//PnxMQ3/uJKzfPOTXfeDc36sIGB0l4WkmMVY4Q/7KObkYesHxdm90LmQRTxNxAiJw6c4SW5pQtnLvfjJDUgQCHnOGX0mrTOPrm3ZxYTyDQjj5HDy01ANmCAaoSnh7CguqW5wJMaSZJYc7huN2VIUtZRVyXzJkU09cQOjOJBIfgeqJkDVXCCqtQ3OBFUMUMVLRHVmXLWGRnUAjPDMmGcDjAz9GM2MR84MeWTNjEfXACNLP0Yz45E7dOwcACN7ZjzyAEbPYvhyZDgALfHdvAtJN8BlNCNKNJQiP4k3mRgGAici5B8KFGORHN/IE2kchsVjenXQ1dITzXgrpF7f3zj3lVym/eZAIdZR1GWbaxsoDOEejZn2SBqr3qilMqAnkoU3RcXS4N/OVRSYTsEpIGLHvyTmL4OvxWDtWdX47tA+e3dsjz4RFosPLnR91yiDhKBGUlQhijKfbQh/DYF+lZ1pQowkPo+/dV+jT0vyCZ9oLF7wZIGATMU0c7pjAZF3tYsoZSI1J0ZImajEAUz0Qpr6s9/AI1iwjcwjtDasNpXQmDzSxQ814byYH+pE3sT0gJXnnOOBJCKyIBENw3EwHqqQtbvaNWD8tvqiwVjxG8FSdhwrnrvdAb98faHdqRNNbXewip44vDuaw7ut1X0PRyNvrfgKa2ImwV6Dt1QPXvwN5MEto18r2jw4bHe8ZvBB4ud5HJy0+VfxI4eY1yYvjkuLt+WosfdiUJn7+ZZdmsb/5hRziRCmao+XRojJM3fY/BmdMeiHZIw9EGNAUTA1Y2Ar7N36GK3pQ932eWv1B3yM2gEcmzGwMfhufYxexmCFMfhSH6OZMRXTX2NMk/WhM9ZUp0sKkdNVQp3ttTiAzb6sUI0Iwy38w/Zc5OdRKlD64XN4t8t0bMKe/KQpfMX0Afe+6CuIasfWQrMyjzaaqp8aDs3hd2eoaGrZraHi7OrGGRaesMO7FNbWGbY2NGFRBrsti0GzpykyLZow/VyOR8WGkk3o9qgYpmYLRlO338TD73TVh6Z2v4mXnDsBNHX7TTz96tHac5UFpHr8ntb11c22ptrCO3vfhzKR63XnGblMx3BVaByGzF2har/kYoWqE028YIzPSeMX0iIYcKOG11UK6skaJu0RYA2LNppa8CUjtTlqZdGm7qS/1VGjiQ3bgln7aIx5P9pX7f5S7YN2+tTah10w2zHW+e7RZ8w/XuW79Ir8s/OT/Op5/u7+jEzhbQ1811R229g9O/PqdP07vLsYNj+sLJXb/DoV3/8H \ No newline at end of file diff --git a/assets/drawio/575.distribute-candies.drawio b/assets/drawio/575.distribute-candies.drawio new file mode 100644 index 0000000..1f89239 --- /dev/null +++ b/assets/drawio/575.distribute-candies.drawio @@ -0,0 +1 @@ +7Zldc6IwFIZ/jZc4EL7spVrd7U4725le7MzeRQiQMRAWYtX++j2BpAraaTvrgrvai5K8CTnhPOckSAb2NN18KXCePPCQsAEyw83Avh0gdGON4L8UtrXgeE4txAUNa8naCU/0hSjRVOqKhqRsdBScM0HzphjwLCOBaGi4KPi62S3irGk1xzE5EJ4CzA7VHzQUSa2OkL/TvxIaJ9qy5d3ULSnWndWTlAkO+XpPsmcDe1pwLupSupkSJn2n/VLfN3+j9XViBcnER25Y3t0nLxm988to+/jwM/2efAsMy62HecZspZ5YzVZstQsKvspCIkcxB/ZknVBBnnIcyNY1MActESmDmgVFNRwpBNm8OVHr9fEhbAhPiSi20EXd4Iz8oZqWChpbVdc7AraeebLnfWQqESvq8evgO8dAQfnmM35yzs9P/tk56X0fQRLkskjTKu/2PSJ9QSHxxozGGWiC53vqPV4Q9shLKiiXrQsuBE+hA5MNExws48r/U854Udmyo+oPulTGxmVerw8SDtaViG4ksYmaz20ihFxYxtIRaB6EmTOksLREFMgWwwAsonmIBYaL1Eu4RgUhRiDNGkFS0FKkuDR0I4K4mU9xFm4NC42GeRafALylF0dF3jMPyLvmIXitnZw7ulzujGakM+yj88JuXy72mG3zpCvuqMXdMnsG/4G98J8Db74LfolTwgjPjCoCYGNfGnrHR/NqST49ebO5xVtuz+S9/5D8Oe7wLmqmvHv4btcpd//KvRPuHmomvNMz99GVezf57ja5j3pe52+u3Lvh7rRe7fpOeP3L8kr+b6/0TuvVzuyb/PXrTTfkfbOV86jnxd664O833ea83SLf92u9dcGfcLrNebu12ve+zx/7Je8xMDsJ6XMjArxfK3lENYl4JoyyOqADZ5vw+JvqHEO3QylW12qcMsfZnw3kTlzfHbhwq3kLlAq6WAkCFUmHklIbAgfUtpr2JXf5KFptxTXAFM1wBgt8SXQ4ZjyT4R5RxloSVuEeAHxSHMmDlIahNHP0DKh5SiR9oQ49ka7vJYQ/G3sz8zTHRXZr37Gtw30HOUeCEH0+CKG6O9is2vZOh+3Zbw== \ No newline at end of file diff --git a/assets/drawio/62.unique-paths.drawio b/assets/drawio/62.unique-paths.drawio new file mode 100644 index 0000000..2e0bb60 --- /dev/null +++ b/assets/drawio/62.unique-paths.drawio @@ -0,0 +1 @@ +7Vvfj5s4EP5r/NgIMBDzCAnpSe1Je7cPvT6dHHCIewRnwdkk99efDSb8rMrukYSqiVYKHnsG831je2bIArjYnT6meL/9nYUkBoYWngBcAsPQoYPEl5ScCwkyzUIQpTRUgyrBM/2XKKGmpAcakqwxkDMWc7pvCgOWJCTgDRlOU3ZsDtuwuHnXPY5IR/Ac4Lgr/UJDvlVPYcwr+W+ERtvyzrrtFD07XA5WT5JtcciONRH0AVykjPHiandakFiCV+JS6K2+03uZWEoSPkThj7X+khp/0tVrsHI/ul+Wnz55H5SVVxwf1AOryfJziUDKDklIpBENQO+4pZw873Ege4+CcyHb8l0sWrq43NA4XrCYpbkuDC2CQlPIM56yf0itBxlraNuiR02ApJycvvtk+gUv4WiE7QhPz2LIqekrysUMTbWPFWGWEm1rXJUyrFwkuhiuUBQXCsg3gGqMDOoIENkTgwhODyJ9am5kThCjFkTWnSGyJw8RvLcXzacP0b29CE0PovZ+fW+InMlDdPeFVnr1lDG6txvpUzzTpnao6db0Qbr/ahv76G9lLhsUkCDoy1zWyDIt7Uqw3t33BoQLl0RWghDibHuBuAZnE7aEJULZi/GaxE8so5yyRIgDARMR/Z4EkYqk+3NrwJpxzna1AW5MI9nBmaQPq9bFjpjaXs5yd4pkOWK2ywJMZiHZpyTAnISzPcvEyL/zykCXdE3zFitDPhUVCmoOCUslFdcIn8uV/gO2jWuxbQw4sh5sv5ftudVk2+5h27ol2wOKPb8Y2wRn/DpkG92N3OhZ2vBqZA+osDzIfu8+bjTZhhocxLaJrsX2gLj6wfZYbFvGfdmGE8w0odGMbXRtWGxzvQrz2K82xgBpPjWQjOmBZE7Ok3qOUh8BzwKeBnwLIJRfzIG7EH9UiZy5FDkuQCbwbYA04Hl51wq4K6mPIPCWssvJDbVhFwDyAXtzfStUos722t6FdzQM5W16yWzSvWEJVy9ldaTatZ3XmS+1+XykxaG1t9kO7Trq4d2+Gu/dQ1X/OWlaraD4jLXRN2lyujSZt8xhYbda9mBJnDQTY6lbroMPlsypraVu9a8bD5AwIs+qKYslLGIJjv1K2sKzGvOZyfA/J/Yb4fysUMYHzpq0kxPlf0n1maVaX2s9y5OynDfOZSMRCNSUZPNrs1kp5q2G5hNJqQBROs87vEBi8lYfSEmMOX1t6vVRqlSfGBUWqxXumK0wqZVuZOyQBkRpVY7xQ0NOyw7HaUR4x07uYJfH+R8+1/P2VYRJrgkcW154OkC5xPOB68qYCvkqglIRmAk8JOMrEUrJCEwH/gp4CxmDyXBLhF7WG+1YwCnUCztOI5ATcZ2XT0yMdJxyzELaEbd2UR4IClMuMOyYK88B8gdqpfPYLwdWpKZws8ndqCayo/y7jCZrs8rtixspswLswnJL45eKP7X8M1Kab7dKeD3VeVu/5XZs9mRn71garjnO0pB2prY03Mr+Y2lccWm0Xlw53XrnjZdGX04OJZWo7+LBYKvyBM3u+4kbM9jNrpdPD6IuZawyrOt5kXTTnMDs5te2MTsk9OVAPuwx32YDSLulp7erhx349L7qofl2/ESz+vV+EQ5X/wMB/f8A \ No newline at end of file diff --git a/assets/drawio/73.set-matrix-zeroes.drawio b/assets/drawio/73.set-matrix-zeroes.drawio new file mode 100644 index 0000000..c2f21cd --- /dev/null +++ b/assets/drawio/73.set-matrix-zeroes.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/assets/drawio/85. 最大矩形.drawio b/assets/drawio/85. 最大矩形.drawio new file mode 100644 index 0000000..938c77e --- /dev/null +++ b/assets/drawio/85. 最大矩形.drawio @@ -0,0 +1 @@ +5Zxdk6I4FIZ/DZfdJYQIXKqtMzU1XTXb3bV7OZWGIKlGYkF6tffXb6JBIcepmq1VQ0e8EE6CwJNjeN/w4aHZavulJuvikWe09IJRtvXQgxcEfpDE8ktFPvaROET7wLJmma50DDyzf6gOjnT0nWW06VUUnJeCrfvBlFcVTUUvRuqab/rVcl72t7omSwoCzykpYfQvlolCH0UQHeNfKVsW7Zb9cbIvWZG2sj6SpiAZ33RCaO6hWc252M+ttjNaKngtl/16i1+UHnasppX4nRVentDXx6YW0+TbH8/bp5z9+fPjTv/K36R81wesd1Z8tAQ2BRP0eU1StbyRreyhaSFWpVzy5Sxp1nvuOdtSualpzspyxkte71ZHGaFxnsp4I2r+Rjsl4zSmr7ks0ftAa0G3vzw4/4BM5hrlKyrqD1mlXWGkKes08yO9vDk2GtahotNebYzoNFkefvlIUs5omP8BLLo42Byrz0mwu0mtwSvRie+n8wAPhgZ8DID7gLg8XNGH3GdX8YoaoHWIlGxZycVU4qEyPlXwmOwmJrpgxbJMbWZ6qlVr/l5lqg0fRudKd9ynH0L64Qn6waXoR4D+yGH6eDQs+vEt5X4wtNxPbin3g2hgud8KxdvAj4KBJb9/eQVpV+iYp1p0gvhVhY4fOE7c7ODtE4diHiJ3povx/X4Pj3zbXUx4Sz28j/HA8ONbyv5gcNl/U8bWlJf28UNn6zB+U17axw+trVtiB8jLxLbYgXb23MSvMyQMZKRtsu2f5POTNSWKdbKuXMZAg8tZ182m2f/iE2e86xJ3/8rR0IhDf4kAcmcknmnvw9iyxAugv3RYYZv23j5+6C8dzn7T3tvHD/2ly6Mr0dDwQ3/pMH7T3tvHf3mzOTB5ObZ9X5IzJnRwZF0xoaa9t0/28ibUjr23T9Z1s2n2v2PbZhNBs/k5c9nsf+2ThT7SsVzGQyMOreMnzeXBkYWu8HOSNc949sle/oLitZ546JONsGWyIXQWIUDrjJU2h1GjAOK/qpUOof24obuk7OOHHsVh/OYwqn380Mi4PIodDQ3/Td2iaQ6j2sd/eetjR9TEI9ui5vJC3PLVeDw04q7cfwZy2bZAx1CiOJ7L1om7Mthn5nJiu5fArg/2mblsn/hvDPY1BVmrWbbavVWjS9tUbIKvO9Hv5JWWP3jDBOOq9JULwVeyQqkKpiR9W+5UXLd5dpOsstvYpG3M0amW1fvzUAihXhsyUSSCRZpV6J6lvMqZ1If1fSq3GCwyIoj8UvFGfuclEXckVfvV3O2C6nnfBVZP4S1eWPr285HUb3d+EN+vq+UZGj40rBVGsOH99raFbssfgudveiiCYnwvA9587CUzL5Yz2JuE3iTy5pGXLLxJoiLTB28Ck0TbABva3/i/RvPJeN7G9atn/PGZekzDIyQnbrU4uOj/aRLk4vE1Mruyzst40Pxf \ No newline at end of file diff --git a/assets/drawio/88.merge-sorted-array b/assets/drawio/88.merge-sorted-array new file mode 100644 index 0000000..bb1af35 --- /dev/null +++ b/assets/drawio/88.merge-sorted-array @@ -0,0 +1 @@ +7ZxRj6o4GIZ/jZc7gZYiXK7ucU+yZzeTTLKbzM2kAx0lC9Rg5+icX79Fi0I/T+ImyldGnIvRjwLyvoX2eYNM6LzY/V7x9epPmYp8Qrx0N6G/TQjxaRzpf3Xl41CJguBQWFZZahqdCk/ZD2GKnqm+Z6nYdBoqKXOVrbvFRJalSFSnxqtKbrvN3mTe3euaLwUoPCU8h9V/slStzFGQ6an+VWTLVbNnP4wPSwreNDZHslnxVG5bJfplQueVlOrwrtjNRV6L1+hyWG/xk6XHL1aJUl2ywteX79XTQjz/iMnj8+Mf/Pmvby+/kMNWvvP8vXvAG/XRSFDJ9zIV9Va8CZ1tV5kST2ue1Eu32nRdW6ki1598/fZNlsq46DPzecGLLK/9/1tUKS+5LpvdikqJ3U+Pxz+qpLuXkIVQ1YduYlaI2WEN07F8ZoTfnmxiRvpVy6HA1LjpGMvjhk/a6TdGvv8hJQVSkqFI6QeOaRkALWkvWr5leT6Xuaz2u6BvUSKSRNc3qpL/itaS14gFzLuS+q71ZAbU93pR/wpaEtd6cjhgLV3rl9Phakld65fRcEcrq1seNcKSMgZSsqFIaQ/86Fo20/6WmOH9jPz48vtAfqD+kWNqEVK+WR2daKnela2UpV55lvNXkT/KTaYyWepyomUSevmsFjHTzPXNavAqlZJFq8GvebasFyhZu8zNp+N29Fdb19+y2C1rGn0oNgkXD6lYVyLhSqQPa7nRLV/2YAhN97zZfEGudMEPve4F34PWkjPWsptZC1Evea/2h2M7rI9aXWBnWz1TAo7YxhVZmta7OXvWds/r+kRtmbNYzNjU657Q5Fpmsa5ZUx94FZzxitzMK8iSQ8XywI+QL2mQJQcz07GHZ3wxIRreEZjjyz9kmnSuLw8YJ927ykKeRJm3p0xEaXDuwhCRVxqGt6F5fPkhgw5mkLO7coycjBCIoP3wPM4YZ3dldPkhguIkAKz+Oyd/uH9BQjm8bjT1QLcF4iNwZUwGLho8/C5s0jNRcK/JAIG0OSYDjVmeZRaFMU6v0QCBODvUaGCKPWsikGYHM2uyx218MSGb3lE0gC//kGnWub4MaRYFAfqhWfeuy5Bm7yhMQJefQgDuZ1hEuZLbnR8btCjkX5z4AZd/7ZMC3RbIv2Ms4YAtkF2BK2MscdEswLphIcSOJSgk3TGWaMyyblgIsWMJClF6qLFEHGHPvyBJf+L5lz3S48sPSbqfIOMG4zO+mJCkUVKhnkjaub6MdW+yE0EGuvwBJOk7CjLw5Yco/YkHUvvXOl6IzGYBROYxyXDBF8jMY5Thgi8QeIEtY5Zx0UTAusUiJshZRgDxeMwyGrOsWyziM7+U6TXLCCB/95NlODFtIGyKfBmE/N3PrO0Wgz2+mncF4GBQx9cfEvgn1h88fgFdfwYR/I4SEAf0d+R2bpwIxAH9IYTjZCCosGefFpQiR1MMMviYjbjgC2TwMRtxwRcI0MCWMRu5yFv7wRR+DL3tNRxhkLfHcKRZgdlunUkpr5SO6I+np1Hul7We6Um//Ac= \ No newline at end of file diff --git a/assets/drawio/912.sort-an-array.drawio b/assets/drawio/912.sort-an-array.drawio new file mode 100644 index 0000000..8d54901 --- /dev/null +++ b/assets/drawio/912.sort-an-array.drawio @@ -0,0 +1 @@ +7Zxdb6M4FIZ/TS4Tgc1HuEyyzaxWWmlHlWZ2584b3AQNwalxmmR+/ZhgoHBIp2FIcCX3og3HcBz8vLY5x6YjvNgeP3Gy2/zNQhqPkBUeR/iPEUIBtuXvzHDKDY7n54Y1j8LcZFeGx+gHVUZLWfdRSNPaiYKxWES7unHFkoSuRM1GOGeH+mlPLK7XuiNrCgyPKxJD69coFJvcOkV+Zf+TRutNUbPtBXnJlhQnqztJNyRkh1cm/DDCC86YyD9tjwsaZ21XtEt+3fJCafnFOE3Eey6YfVvG/y7/+0K+zl9Ou2/T57+eP4+VlxcS79UNu+rbilPRBJztk5BmXuwRnh82kaCPO7LKSg+SubRtxDZWxU8sEYqi7anjBYsZP/vCy+XUsixpj8n/NJ4zHlJeFCcskZfN1zFJM2jZWUzWE4lMOtPsMBWcfS85oNLyqoLg/CNL1I1RLujxYovZJQepX8q2VPCTPEVdgKZefonSrqdIHiohuMq0eaWBwkaU9Nal44qO/KAAXQELAVi2gVXAwlgvWNjAegOWZj3LAbCQgVXAcjTrWS6AZRlYJSzNepZnYF2G5WrWs4K7wwqCxeKjwHJrrMoIZShYRf2GVhst29INFwy1DK4Kl3a9CwZbt34k/EC4mpHx8LhguGVwXYyNh8cFA65bR8cfCZd2vQuGXJPJBACTdyzqVOrNpBr6KYrjhonE0TqRhyvZQlTa51n7RSsSz1TBNgrDrJpWGVRCsRocX6kCW2+oosG7jxGymO9Lhi5g6LQwRDdj6AOGI+TFGbB0R5IaSe95n2Wlz801Ts/tN5Mn2N7uWBXKT+vs79i1sohL+ZLfLXeXFxqNXBFRBLCb31ci01tJxDkPjkYi1z8XF0O/LhpBMOrsUSNTo5EOGvF00wgMdfvRiJll+ojVBpcHDK37kYdt5NEh2HBszeQBQ/l+5IGMPDrII9BNHjB10I88sJFHh+VG7UYPmKroRx6OkUcHeQS6PXvA9eh+5OEaeXRIgDi6yQMmycwi3cWNQIPnpRFMWBlcF7cCDY8L7i8xqz4XNwMNjgvDVJ9Z9XmboQd2nUyHndAwzLWYEbLC5TmadTmY+zC4Sly+rRsumIswuCpc2vWum+UGzNJVTwPw0AEgvlV+wKyAdx70A80kcquNNGYfTeeJRjeJwEAXAKRJOMveT85IZK0YrS4+CbQ3p3rPpT6p47ZJfbmcu37V1jQErzz/sqXfOWVzGhMRvdTdtzWvquEfFsmKq8Umqx4C4ymuu0jZnq+ouqqC9GtHqOFIEL6mAjg60y5v+zfeJ2zbJaOBAEKSbspHUe3V0NjX0l0NTUf3VkPbfhijhuvU4NjWxG3owemmhxZXyLmvImBaxijiakV4Tm+KgK7urYi2XS9GEdcpwrV7en4Aju49Y7RtcjFquFINfk/PD8DRvdUA81aRDMKOQBJaB4WoRYT+w8x7aMlUjn4/KGy+W6F61FAhoQMTSyvZasIwfD9DNPArVA7M/CT7bWoQvrX7HNXHTn9ohnDLSGCjScq4GJNkTDgnp0tAdaLo3JeiW6foYJhhw1YAOfrXc5SH1T/Fy6fQ6j8L4oef \ No newline at end of file diff --git a/assets/drawio/backtrack.drawio b/assets/drawio/backtrack.drawio new file mode 100644 index 0000000..1114526 --- /dev/null +++ b/assets/drawio/backtrack.drawio @@ -0,0 +1 @@ +7ZxRk6I4EMc/jY9rQQIBHkcd7x5m665qHu7ephiISi0YD+Lp7Ke/oEGBzuy5iia6vIzShBj+v3TTaVIzwONs+1serhZfWUzTAbLi7QBPBgjZOPDFR2n52Ft8x9kb5nkSy0ZHw2vynUqjJa3rJKZFoyFnLOXJqmmM2HJJI96whXnONs1mM5Y2f3UVzikwvEZhCq1/JTFfyLtA3tH+O03mi+qXbRLsz2Rh1VjeSbEIY7apmfDzAI9zxvj+W7Yd07QUr9Jlf930k7OHgeV0yU+54KsbvD29fX97eZ4lTv73H68BmX+Rvfwbpmt5w0iOln9UEuRsvYxp2Ys1wKPNIuH0dRVG5dmNgC5sC56l4sgWX2V/NOd0++lA7cPti3lDWUZ5/iGaVBd4UjE5ZWzi7o83RwCubLKoaV/ZQol8fuj5qIr4IoX5CZEQEAlrF+kwrUwRCQORiH6RTJtJDhDJ0y4SNm0meV3HpDgsFru2VwpQSLdi/h0EKO0iBXcQoLSLVKVdRkco/Sp1njZdO0RhT7dknSdRXUvWDlj6JbuHlEq/SveQU+lXyb23iOUEuiUj9xax9EsGU/cLI1bnkhk3y2Dubl740q8STN4ND1/E1l20gpm84eFLv2QwrTc8fOmXDKb1F4avriVrxzL9ksG0Hih2eFdg1RWxmkoVPGff6JilLBeWJVuKi0dp+E7TP1mR8IQthTkSMlFxflSKmERh+tJq8M44Z1mtwVOazMsTnJVkQnl06EcMbVWOMtvOyzc+w6yIQjqM6SqnUchpPFyxQrR82718Ee1nSZpWYxwgbFmj8RR1FHN9t4HWtiBadFO0cC1yIlr7R2iPsiHLdMRyGnYR67BpdOGCoKd7Nl3jfBeuXXq6Zz90jfNduMzqH7qdhGWkyKduixauDXvHPT8sW2bRxXAZ29M9PyybRheuuPuw3Ek+hZFutLAy0KPtJJlysG60Z1cw+pj8vzFZv+OeXcR4BMcNvInled2gddwmWtuxAFrbVrGtnsvdw4XvgQseRt8AYXHX/AScCp8ARNrgsiSOy59RVoKbteJG4JixJZcbxBGRxzVy0ymxdvYOyLntXQ4YknMU4NDVnPLs2tMjOOV0OglIiTZOxAVyDBta8G5ok/YOWFe/n8JqFKfZ6iUR91w+HSflX3c0cCdgGhw9yP7Ex370BI7JO3EJRDCbzVAUKbywA/0P2lYJjkJ+TyE/uZq3wWrR46rvtF+QKXZX3lh+VUWHpFzef0Nw8s+aVSe+FDtlnkQDO1htjyfFt3n5CSAOh8OqZzHSfef7pg8AtuVVB151rqrM8mpcHVUtp+d6IVesn6uqitNzvZCrazm6uapKOD3XC7kS/Vxh/eZx0xs3aCWXurMbB9ZXluusAFKbsQK/4aIbB7hByvY1L7odWCzpSalIIe2kYHmkJ7Un1UoWA92kYGGjJ6Ui5dq6ScEiSE9KRYroLg47sF6yS6g/zd1O2IYNcjcfvWOiyN1il/qxc53crfXi01fsIrvp1u2q45rQ6S5p/tVdgngtUrqDl3vCxpLL/KH+qloB8obi+6Z5iaps8KDa28aFqBP2ZjyM+MbN/F9668QT8a2R31HVBg2bbIkbDCHdyvvqdO1qU+FP4BWHx3/EtjtX+3d2+Pk/ \ No newline at end of file diff --git a/assets/drawio/binary-tree-traversal.drawio b/assets/drawio/binary-tree-traversal.drawio new file mode 100644 index 0000000..0767052 --- /dev/null +++ b/assets/drawio/binary-tree-traversal.drawio @@ -0,0 +1 @@ +7ZlNc5swEIZ/jY/1IIkvH2snaQ/tTGbSmaS9dBTYgFpAjFBsnF9fyQgbUNLEbWx88MnsSiyr59XHgidkkdefBC3TrzyGbIKduJ6QiwnGiMxC9aM968YT+KhxJILFptPOccOewDgd431kMVS9jpLzTLKy74x4UUAkez4qBF/1uz3wrP/UkiZgOW4imtneWxbLtPGGONj5PwNL0vbJyJ81LTltO5uRVCmN+arjIpcTshCcy+YqrxeQaXgtl+a+qxdat4kJKORbbqBPP9xfgFe382yVLKv1t4vk6oPbRFnS7LE/4EquWwQqjKKtjPkqZRJuShrplpUSXPlSmWfKQuqSVmUjwQOrQT11boKDkFC/mDXaslCTCHgOUqxVF3MDCQ0+M388Y652YrSutKND66NG/mQbeEdIXRhIewDzLGD4tIDhWR8Ywt64xHyLmH9axEjQJ4adcFxioUWMnBYx7AyJjbwqZxYx97SIITQgFoxMrM2ng8w7LWR4sPNv7dGQIQtZcFrI3MFOhsjYyLCFzCJWclZIEJdLNcjKwNnWSo4yYlqlmtDG6BCspOC/YcEzLpSn4IWmntF7yK55xSTjhXJHoIOrBg2Wqbruy6DDPZeS550OHzOW6AbJtWLUWNs4KrVSZ57Xia54p3kVUZjGUAqIqIR4WvJK9fy5KT5V/weWZW2OE0wcZ764UhLOBZfUpOA573SQeW5P/sA+x/Az6vv+odS3q8uz+gdTH/uDxe/Zhd9x5bdr5bP8B5MfDavYZ+r+48ofvC4/FPHHBlUrYUdiqJm861x/71xf1GZKbIy1MZr4EFuv9AOIKgf+KCL4S/Zm65RUJCBfraNsVd544grI1ERY9vN9TgrzhGu9Xjpr3h2ITgYneTNOc1f348AgEBlU9AQNAjUgrECbmbEd9n9MFvudZ7/J8lbpx1KKkMHu3H4n2lup0JmGm3iq2iPEJ/0JgMiUdFpxeFwZ7RexPdd8oRK4a9e2NvSqd6bIb+3dyt9Y6651DYKpIeiteq/9YKxJ4bafQox6M/Rvc8IN+nGQe9zVi+2XyfNJb530enN+nzrf2vXJuEc9tt+Mz/ofTn+XeP3l7s0Opb8yd/9QNNvF7n8ecvkH \ No newline at end of file diff --git a/assets/drawio/call-stack.drawio b/assets/drawio/call-stack.drawio new file mode 100644 index 0000000..43fe8d4 --- /dev/null +++ b/assets/drawio/call-stack.drawio @@ -0,0 +1 @@ +5Zpdc6M2FIZ/jS6TQcgCcWkctzuzbaczmdn2VgYZaDFyZXlt99dXwvIXkmNnC8GOyUwCr8TXc06OzpEAaDRb/yzoPP+Vp6wEvpeuAXoBvt6w+qOVzVYhEdoKmSjSrQQPwmvxLzOiZ9RlkbLFSUfJeSmL+amY8KpiiTzRqBB8ddptysvTu85pxizhNaGlrf5RpDI3b+GHB/0LK7J8d2cYRNuWGd11Nm+yyGnKV0cSGgM0EpzL7d5sPWKlhrfjsj3vpzOt+wcTrJLXnLDYfP06JvDP37+JOBbf/C///JY/mat8p+XSvLB5WLnZERB8WaVMXwQCFK/yQrLXOU1060rZXGm5nJWm2VyOCcnWZ58T7t9euQ3jMybFRnXZ+4wBZjwGYnO8OvD3kdHyI/aDgRGpsXm2v/YBi9oxZN5BybcpjQNAIjAMwZiAIQRkpJWhBwixACoU8pTSQgr+NxvxkgulVLxSPeNpUZYNiZZFVqnDRKFjSo812EI559A0zIo01bdxmuVgOK8dyyB4apnINszAYRe/K7Og23NefHvOO3A47wDEBJDhbifWzhspjw4fwItxhC95MflIL8aWfaacv+HI3mVHPjYC8NF0yoIksSymWtIwmngtcW3GbQyvdH3SFdnAIjuholWyKWYkHbjIEn+CgqAbsgPUN9nQIkvv0GObwbp/rsTFNSh14J0ozw0yWb95U5ncoVM34aNrR8rO4O/i1Vv5hLqMqjzOjVVHTOlivi1HpsVa26ENZE8wOmXmBw5mzhSss+wCOiqIMa5ziEDvqN8E18oLiD5xVvHUMA3sOzmGdtFS5xVenfTFYIjAOKztBFv08kYoYVAFk9AVSqIgRLSlUPIEyeVYEjrgh53Bt0uTOvX4jPDDBnuIe2aPbaRpxl7NIRcy5xmvaDk+qI14cOjzC+dzg/svJuXGTFDRpeSu2lHf6G2O6rn4UiTsctoqqciYvOhktmEEK6ksvp8+SPuYB3eOGV+L2e8Vs13EWNwXOZ3r3UVRZSUb6llPhSothAoPBddjoYKh/xffm4Vj/eMKIEG96TN4JY/07dZS1YMbyY6jngwcgQV6nUUWu+z5AVtU2u3fn5RTRqbOiihICJtMW5rhC26OuV0S1WmlSijxA0xW+Q2D7DOd3tLKyG2PEMTDB7AHCtFt2cO/omj9nOMDbo4Pft+xynfUwp9rfAia40P/zO0y95HGB9wcH/qedvAdi3IPND4EzfGhd3vYy396JsKxfH1+TbBprXdNLrfh5FdMFbuW7Lr7HsC9ZnfXUF2LHx8L1VHpmsjxUmPDIPZ6x3bNWtzHYnMUpbePzbU4/LHY7Lry+fn5/3G6iUXM/pfd/TMlokrJiN6JVWj07ik0duis6vDwBWDddvQdJRr/Bw== \ No newline at end of file diff --git a/assets/drawio/coin-change2.drawio b/assets/drawio/coin-change2.drawio new file mode 100644 index 0000000..eb01a01 --- /dev/null +++ b/assets/drawio/coin-change2.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/assets/drawio/data-structure-queue.drawio b/assets/drawio/data-structure-queue.drawio new file mode 100644 index 0000000..218c49e --- /dev/null +++ b/assets/drawio/data-structure-queue.drawio @@ -0,0 +1 @@ +7Vrfb6M4EP5r8tgIbCDksUk3uw930uqq0949VQ44xCpg1naa5P76G/MjCdjd5CRScm0jRcDYxub7ZsYzAyM8z3ZfBSnWv/OYpiPkxLsRfhgh/fPhoCX7ShJ6XiVIBIsrkXsUPLJ/aC10aumGxVS2OirOU8WKtjDieU4j1ZIRIfi23W3F0/asBUmoIXiMSGpKf7BYreunQJOj/BtlybqZ2Q2mVUtGms71k8g1ifn2RIS/jPBccK6qs2w3p6kGr8GlGrd4pfWwMEFzdcmA6K/o2+Om+PE32f/5PeT0JfIf7jCqbvNC0k39xPVq1b6BQPBNHlN9F2eEZ9s1U/SxIJFu3QLpIFurLIUrF07r21Gh6O7VhbqHxwe9oTyjSuyhSz0gmNSI1Srjh/X19khA0KC6PgEfNx1JTXpyuPcRFzipofkPMDXaeEswHRZ1QzCdRwmsoNCnLCsN7xQTjQYDy7tPWZKDTPHiRPobWdL0O5dMMa5bl1wpnkGHVDfMSPSclAzMecpFORdelT/oUk52L4vKQWh6SHOxYjvN2axez8NaKe1Z7jUQaBHFuTtm4FtWDLgV4whmRIuYKAIHLZdwfCDi+WtKpHz6g265eIb7oYWLwh384YwUhSwH0Qz6j4s86YF8F3XIN7kvZ+9yfxD2zv0FjuR/x713lnvCEnIn99mSp3cSlq5lgYvqlidFduxpmdwB7H0R77eJP2wvJ8yHFqO/ms3jT96H4B27A/PuffI+BO+eMzDv/u1FQh2IgqkJEZ5aMHLda4EUWEAKUph2tuLwTDpdaTQ3+LnRGQDggDFo8XR6KjpBtRHqG4Dq6TwJFNdBqNidjggSVeKrCmgU9OeGSj0fHGEl9RrgkaplVJ0N/oAJ1SZJKsGfaWNuOc+1Oa9YmnZEpDbnCJijwmLnGYtjPY1VK9p604vx4LZmTLChGcgWJvmTKynGxFAMTZX7CxNyz5tQTOT60LcH2DBuG9TE9Q3YbPYUXMucQitqv3I8F6DWA1Do1oCaWoHCgwPVTVcGB8o1U9WZLhaB03q33hA57thv+0NLMOFZaEBXo8HMGnvwhz1g5fm3prBmnkVfaBlLpByC59di72yX6MLseFlk+bjs2YKKb1TKctDlpnrak655flvTwmDgsNU18xVBEyarVYNNpktIK2wwVkVb53SfdS7wB2knhzGcwKtJjj03MpxJl+FMRoSOY1oIGhFF43HBJfR8KqvgHQcFwabjzOYL1BPbYZvtiSUCt1Ui/auxbQbg4Npf3rN3n3qdLGhw324GI8MmQR8s+Qm9sK0Q4dDJDzKDrtvY7aeo47/QwLs9slVZBrEdWfBcfrzKQdd4QkuZ/Y2N5yqlgysYTxigYY0HX/ZK4r3HdTGDAfUaci40Fb0UW72Or/RMX4kD/w0DDWyrtnb4PkuO8ZLg7MsKq3YYbFoTrC7FEVOC7caF4BGVsqeSi9stTiHPIMpHFrtE1+LJ9GCfdtmbXbpOt8ZmMczJm9qlWbb96HxLcAfrMl5RpBZh1FeN1WnGHGKWqaEAri0Nv5oGNPnmpwYYGtDHTtytsVk+AOtrJ4bL45eKZdvJ9574y78= \ No newline at end of file diff --git a/assets/drawio/egg-drop.drawio b/assets/drawio/egg-drop.drawio new file mode 100644 index 0000000..32393c9 --- /dev/null +++ b/assets/drawio/egg-drop.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/assets/drawio/leetcode-thinking-dp.drawio b/assets/drawio/leetcode-thinking-dp.drawio new file mode 100644 index 0000000..1525d32 --- /dev/null +++ b/assets/drawio/leetcode-thinking-dp.drawio @@ -0,0 +1 @@ +7ZtLk6M2EIB/jas2h3GhB6/jzmtzSFJJOZWkckkxIAMZQA6W1578+ggjsKWG3dmNjcGzB5dRS0LwdavVLdkzcpfvPpTBKvmRRyybYSvazcj9DGNEfE9+VZKXWuL5pBbEZRqpRgfBIv2XKaGlpJs0YmutoeA8E+lKF4a8KFgoNFlQlnyrN1vyTB91FcQMCBZhkEHp72kkEvUW2D3Iv2dpnDQjI8eva/KgaazeZJ0EEd8eicjDjNyVnIv6Kt/dsayC13Cp+z321LYPVrJCvKYDTf56Tn9j2z9/+fVDnmyXz38s+I1Sxscg26gXXmzydz99px5ZvDQc5L0kclm43SapYItVEFY1W6l1KUtEnskSkpfBelXrYZnumBz6dplm2R3PeLm/EYls5kVUytei5M/sqMbDT8RxZI16JlYKtut9WdQilLbHeM5E+SKbqA6EKurK7GxV3B7pUImSI/U1skBZTdze+ABWXii2X8CZdnOWohs0edjU1mEjCmm7HbTdc9G2Ae1ik69n9q01s++nThuPjbbTRxtdAW3i6LS7PMmgtN1+T4In70lsOjLaXp9t4yuwbeo4Gm3ieZel7ffbNpm8bTtkZLQRArhn2MlEBYjLdzqG7fyz4U3FzXofpb+XDYi72h0q5VVcfc/n8+ZG8sHqe9U1QIcSp9CVpeug4AUzFKZEQZbGhSyGEj+T8ttKOakM3N+rijyNoqzPOkq+KaLKFu6tE3ku19a0S13oueig2sVAu4/p0zWE9o5O2vYxID1obI9gErUnfR3BPTaWZKfDbQ2LG+ZSLW75mX4QRB00MuIwn2qJnxP30gtZGHbhfvLsKuk8CW5keG7Xh7idDtzO2XDDMKjFPf0wiKCR4W7254bGPYx1U6zj9qxL44ZRZ4ubTt66bWdsuGEY+C3I/2rf5eqxkNexPTFokI9hLBRmaf70bvpxp7nLiQhkbXewts/GGkZBNevpR5wIjw12M4uOmUYxW6giL0XCY14E2cNBasz3Q5sfOF8p3n8zIV7UKWOwEVzXRj1mNdCnQcrn4psyZJ+yFqebeMmyQKQf9QG68KmuP/N076WbbMxYzpHlGyoQQRkzoboZWmif43/MArjbX8+C6Uem2BrdLKBTnwXueWaBb8wCTAaeBfAUpp4F049gsTu2WYDhIUwN2548bPNnCiOADTcfatjO5GFTNDbYzfgAtjt92P7oYH/LhGenO+6yjRUYIajeQVNhAk9h0BfzP95869uuG50mjFDIep0i8NkUAfck8FtQBDJTs8troi9Xm/7uEDKjVKsD9rDLS19KMP3tIfMIeASwYUrwJpy9eVaJrFf+zOR8PgbmC2/C22M6Nk1QeLB2NQ6IGA4IXzqZoDCZuJZtUGKmyZeHDUP7t+FjiOFjyKUjSgrPwIAidAyfMfmOTDcK1sm+e6cOHx+9Kvpoapr/ZNETra8Uz3Xmvg+Rt6vwMXN0PugwjL8q6Nhy52bq1LGbcDLqsnj41119InD47yJ5+A8= \ No newline at end of file diff --git a/assets/drawio/piles-banana.drawio b/assets/drawio/piles-banana.drawio new file mode 100644 index 0000000..7ad992e --- /dev/null +++ b/assets/drawio/piles-banana.drawio @@ -0,0 +1 @@ +7ZxLc6M4EIB/jY5xgXiJI3bs3al9TVV2d45TGGSbGoy8oEyc+fXbwsIGmeySlLFciZKD5ZaEoL/Wq1sGObPt/qcy3m1+YynNEbbSPXLuEca2ExL4EJLng4S47kGwLrNUFjoJHrIfVAotKX3MUlp1CnLGcp7tusKEFQVNeEcWlyV76hZbsbzb6i5e0zPBQxLn59IvWco38ilwcJL/TLP1pmnZ9sNDzjZuCssnqTZxyp5aImeOnFnJGD+ktvsZzYXyGr0c6i1eyD3eWEkLPqRC6VV//pV8+vvH5y//uPvff0nLP8id4x0u8z3OH+UTy7vlz40KSvZYpFRcxULO9GmTcfqwixOR+wTQQbbh2xy+2ZBcZXk+Yzkr67rOakX9JAF5xUv2jbZy0iBcWuKC8gZoyen+xUezjwoDS6NsS3n5DEVkBdeSjyGNzPMlnqcTMtuVHDZtXJYvTUWayfp47ZMmISGV+RrF+u9BsZ57e4rF70Gx2L85xdr/r1cYwnYimW3rUbOtRaGNDIbNKM/WBcg427Wkv8ZLmn9mVcYzJnKXjHO2hQK5yJjGybd1zazDQfxBkbqxqNodRneh/7j5ssr2gvJU3s/9hnMxLURCEXiRpIUzyWBiWGVgDeUkgRbxIo15DB9CXsHnqnzMeHUHs9TCs4UGl3EB/3c2JpNdsb4AatuxOqixZ52h9npIN7KLgx7QgQzot4D2FNDE0wvaMaDHAR0qoB3NoF0DehTQjq2A9jUP3QNW6wb0W0Crc3SouUcP2D0Y0G8BHSigXc2gAwN6FNCuOkfrHrqJAT0KaE+do3UP3aEBPQ5odXule+huDM+QHrtLY81jt218YyMtx1yFtB1MdPdq4x4bibXVZW2HWDvrYR6yQ9hOaDyNq80xrtCi3o0VFKygDdMW7AQURcsh1tBvRLH8drxOY4bb/VoEXyfbKonpJKW7kiYxp+lkxyoo+bWOg55HOixrOltg8VQZVJD38EQrfpkQh9q3j0G6Fm18VdrGTXadCIdv6R/FjafsOkEO39U/ihtn2XXiHN4NzNjGXzYSa19h7esmbRxmI5HGCmlX9+7aeMzGIR0QhTRx9ZJuLM/st477rYKVAsVFzpR5trIMPx/C3eY8bps3Ho238ZuN07OPR4uOqLWvzBq0hvXF/WYKa0f77hqbk2Vj+cOVY8FEP2vjNbtOv/Z8/WO48ZqNdBrFUT0p+vu18ZqNxFr1kDr6+7Xxml3nlJlH9LM2frOR/GbqWQXdJwqx8Ztdxxeu/UihM8xvZki/lrSn/gwz0NynHeMxG4n0mcdMd58etib7SL5wGl/s7BFWaPdGrq96+sgZti4zvN+0Dld+1nMLvIetzgzvt/D2rZvj7fas0eYEEQdN79HcQ7BThDQkojkiMzR30TREhJzZBCiFDzCAtr6l6IyhinqbpalopveNGN13ZlyA0XHVLBkdo9EtQs3y6yrRSLdnbfWhCbmns3s3wwgbRt11a3BrhHpifwLMFEUWmvsoukeRK5hNFygMa0IERRGahygMBDZIRBiFBM0DFEaIuKJWOEPRrK7looiIC04tNLXrLKi+qJuY1Vl1YRLWZaD6opMAU4BWZKP3deEFijyRgCrTeZ2A6oG0IbiB021AdWh09mpjgvnOTwhdrs7nwTSmZJXcnlmpBwhIzyEwx+s5K0JGs6ueOKNkbdWwwDCCDiywN+K/357vKOf03B7fRNCzwhmv5/dEBz80IdUjHOC+Veh1GZ1H9ezOGBz59agMw6QnRsR3yyYIbo/NAI/PmVOtRWJHywzuhP6X0l/eUTbewhUruHzNpo1bXrtsu4anyrOleDax2YPPT8WKfcWT6vv6QrOO4lgn3mhOOPh6erVmndd6Qakz/xc= \ No newline at end of file diff --git a/assets/drawio/remove-invalid-parentheses.drawio b/assets/drawio/remove-invalid-parentheses.drawio new file mode 100644 index 0000000..464ab42 --- /dev/null +++ b/assets/drawio/remove-invalid-parentheses.drawio @@ -0,0 +1 @@ +7Vpdb6M4FP01kdqHILCBhMembXYfplKlrLS7TyMXHPAUMANOk+yvXxsMicFt0jbQzLR5Cb74+5x7rm08gtfJ5o8cZdEdDXA8AmawGcGbEQDWFJj8T1i2lcXxppUhzEkgM+0MC/IflkZZLlyRABdKRkZpzEimGn2apthnig3lOV2r2ZY0VlvNUIg7hoWP4q71bxKwqLJOwWRn/xOTMKpbtlyvepOgOrMcSRGhgK73TPB2BK9zSln1lGyucSwmr56Xqtz8mbdNx3KcsmMKeHfQc6fmjxmA0/uMsJn1uBjLWp5QvJIDHgE35vXNshzzx1A81qYiQ6kYCNvK2XF/rkTvZ0uasnFRYnfFM/BebXYv27WIzCVgMc2VWkYA+uWvW/aiLsyHV5Wv6+RjNi+rvwslpTM2dVQjUfvFzcqYgTJUwPBG2COWxNxgielgOX3E13IcKU2xGB2J45YJxSRMedLnQGFunz3hnBFOsSv5IiFBIJqZrSPC8CJDvmhzzR2K23K6SgMsMDSbbokK8OZZHlgNu7hbYppglm95FlkAQElI6ZFWTdD1jt/AlbZoj9t1PiRdKmyq3rGOP0jivYKEtoaErdlvnEdMQoCKqJmRQ4DE6AHH97QgjFAtCt9aGR4oYzTRwMRopkOTdy0TvUw2oZBAIyl8hI0AczL5iOHAyGjBc34v1ajFEE5505xdz4EYFeEFZB/WuGAnQttuoe1o0NaA7fQFtjV5K9rWb442rzAgvJ3WkE5BgolKgib9USQAOpfvKe7ICPCe8HP5Yvj5ijtvijuNMn1Y3AFOh4WGYZwpAooMChLKtTK0TwMPBCo8UAOPPSg67legGDpQwFagsD96tQDh4IFCu5sYKHr8+oEipwxJtp4qbrSEyf7w/Qo8bsNSuj9JygOGfVj0AnJQd0rFmiH/MSxnfE8rluWPZykbuyqy6iBEzD+qE0uyERjNZH9uIsbECcqVmAkw94MUGsSn6ZJwLHPD5y2CeYAY4n/CXgjuxoiNUalFxbg0jj1udiwxnX8R//H7HcofxxaYGlkatgVZJWOpcOL3gvL1J3OW7aqxzoMdSk3sLqNq2+kZ1V2JfApG5RijLIt5L8u+cYHjxAFzYG/EDmF+g2PM8NmzyTm8sHUGDZrHbbG/2HSWbGppk2U5H8sm2+uQBwchXsgkzVlEQ5qi+HZnbS0Kdnm+UUGmctJ/YMa2chODVoyqJNTvcETDL88r7ydd5T5+yT2megByzAMceVIb0E2nLHpPSbkYlMC1ls5tPBjKQ8xkmRYkTSfe4fPTjs//XGH+2MZORUa3oNtDoR8v6WH36potDe5KsKXbuLi9eY15WIN/cTeSulARu09/ex8QXcfgWyiUCKanD0VWbcSyVRF18OnuqHLMN5HoocwgEMmEO5c9dmYj5+ZVe6kj4uXRjiK/fsqejRr67+P6Ak2fdauxaUDTnaiL5Sr1VrWss9DlssD9SKF32PX6VkGpdMMJYOernqnZJQ+7brB/M8Gzn0HgnesG11WRmw67cLC7x6yfVgztQ2JoO8BTwLLPXQxt3Q2PzyaGuuWgVgzt3sQQnoMY8unLt/+I+gynTv4r85aJm41srEptR/unuKcRUTDUqvE4tfUGVtvu0cynVVt4SG1d11PVdgzOXm7BZ5Tb9hca3dpzMuTa0+nCcKnZ9V1obLp8ryj7LNbWYazPFN6x1bpDBjXwWkMerjhHfIA7eF598MS7gqO+9WvpFJauWExSDll9+bhzPVGbo303AK0LGBjlTYDFre48TdKgc1reB9r1t6g6QNodsC132gXbc18NNk/urj9XCry7RA5v/wc= \ No newline at end of file diff --git a/assets/drawio/searchMatrix.drawio b/assets/drawio/searchMatrix.drawio new file mode 100644 index 0000000..4d1d22b --- /dev/null +++ b/assets/drawio/searchMatrix.drawio @@ -0,0 +1 @@ +7ZrLcpswFIafxst6ABkblsGN20XTWxZddmSQjVqBGFCK3aevMBeDjtNpZixL00kWGXQkBHw66NcvM0Pr7PCuxEX6wBPCZp6THGbo7czzQjeQ/5vAsQ0slm4b2Jc0aUOjwCP9Tbqg00WfaEKqSUPBORO0mAZjnuckFpMYLkteT5vtOJtetcB7AgKPMWYw+o0mIm2jgbc6x98Tuk/7K7vLsK3JcN+4e5IqxQmvRyF0P0PrknPRHmWHNWENu55Le97mmdrhxkqSi3854aG+C8K4/uRHX75+jNkPl9bbN17byy/MnqYPXIljj0B2I2nLQlSnVJDHAsdNTS3HW8ZSkTFZcuUhrop2CHb0QORVox3PRTekMg9Q1F2MlIIcnn0Kd2Ajc4rwjIjyKJv0Jyw6nF0+BV2xPg+O34XS0bj0Mdylw37o+ExMHnTQXgAQAYALuwEOb5YlABcA4MpygIFdAH34Clv+DiPfLoJLSNC3m+DCs4vgChD07AaoyojrGyYYAII6U3BHGVtzxstTx2gXxCSOZbwSJf9JRjXbwF/IZ9aiPMaZh4B5YHfWqtJjnGA/ouOp0/I3XxUf8wxdyDC0m6EqP+YZQh+DNCIE06TjrDYb+QBRgqu0OeXUjwadGtykMdLQ8Cw1kp7wvIA9Wm88PepknjR0RpZPC6o8mUd4wRvpzFYN8mSeIXRHti/uVXkyzxD6o362+d/0KTSNGhopV+dSQIfyGGcIjZFr+66mKj2mGfZTzpih5RubQHqMM4TOyPbddSA9xhlCZ+Tq3OVQdpauuUJXtQYtTbOFXsj2n89UrTHPELocrdZdg9aYZwhtjlZTrkFrzDOENgfpXKJr0BrzDKHNAQSHryCcsRtxpvSm/iXneUOc4S1hn3lFBeW5DMcSE5H1UQORxph9UBpsuRA8GzW4Y3TfVAjejBbuSkM/8taK5i6zw775lGWeVTEm84QUJYmxIMm84JVs+f30WclfpA5IoiyP2jV/rRejsuPuXmtSiSuppL+a+9O86PeNRnnR74yM8wJpywvoyV7z4uZ5sVRWpis4W9w0K/qOX7PixVmR87IZsqukReAo04WHhglklBrogpD06XL91ICm5TU1bj1hyBlCyQw3hHlxpSlDFs9fQp7qRp+Tovs/ \ No newline at end of file diff --git a/assets/drawio/word-break.drawio b/assets/drawio/word-break.drawio new file mode 100644 index 0000000..56ebd51 --- /dev/null +++ b/assets/drawio/word-break.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/assets/excalidraw/excalidraw-2020327114745.excalidraw b/assets/excalidraw/excalidraw-2020327114745.excalidraw new file mode 100644 index 0000000..a4627d9 --- /dev/null +++ b/assets/excalidraw/excalidraw-2020327114745.excalidraw @@ -0,0 +1,406 @@ +{ + "type": "excalidraw", + "version": 1, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "zDUt-OZ13znlbrzQ37LFH", + "type": "rectangle", + "x": 556.1015625, + "y": 277.5390625, + "width": 379.33203125, + "height": 114.9296875, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 433190497, + "version": 350, + "versionNonce": 946401039, + "isDeleted": false + }, + { + "id": "lw-8lETgAHSFfS7cAoWQ7", + "type": "text", + "x": 721.3046875, + "y": 413.953125, + "width": 42, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1941730607, + "version": 121, + "versionNonce": 298235137, + "isDeleted": false, + "text": "1680", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "CyxfUg4JbZHaJRm3DriEr", + "type": "text", + "x": 994.265625, + "y": 323.47265625, + "width": 37, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 807765057, + "version": 202, + "versionNonce": 668372047, + "isDeleted": false, + "text": "640", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "21NXldLhUrOqftoAbe8F2", + "type": "rectangle", + "x": 555.5625, + "y": 503.06640625, + "width": 141.76171875, + "height": 114.9296875, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 964597487, + "version": 596, + "versionNonce": 483754255, + "isDeleted": false + }, + { + "id": "TotS-hk5hni3aBfsRJhYs", + "type": "text", + "x": 610.56640625, + "y": 633.06640625, + "width": 37, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 231197327, + "version": 371, + "versionNonce": 1676021775, + "isDeleted": false, + "text": "640", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "3L4JpanuMPC-zzY8gt_pZ", + "type": "rectangle", + "x": 699.361328125, + "y": 503.8515625, + "width": 141.76171875, + "height": 114.9296875, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1804052463, + "version": 644, + "versionNonce": 1807779631, + "isDeleted": false + }, + { + "id": "OAQNo7RTRh0twKleD0WU9", + "type": "rectangle", + "x": 844.423828125, + "y": 503.76953125, + "width": 92.9453125, + "height": 114.9296875, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 540432399, + "version": 733, + "versionNonce": 332531521, + "isDeleted": false + }, + { + "id": "iOdu8hY0_fQ0CtyMdlAe8", + "type": "text", + "x": 745.89453125, + "y": 633.76953125, + "width": 37, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1691672385, + "version": 398, + "versionNonce": 1095629921, + "isDeleted": false, + "text": "640", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "kEfsrh2Cd4ten3YUGqwdb", + "type": "text", + "x": 868.86328125, + "y": 639.40625, + "width": 43, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2136340193, + "version": 436, + "versionNonce": 1072117295, + "isDeleted": false, + "text": "400", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "qtgpt9hVX3rL8ZskL6_Dq", + "type": "text", + "x": 989.97265625, + "y": 553.18359375, + "width": 37, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1686400737, + "version": 282, + "versionNonce": 1340236833, + "isDeleted": false, + "text": "640", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "v_9X_wTqANOvptYLoXK8F", + "type": "rectangle", + "x": 546.7734375, + "y": 724.87890625, + "width": 92.9453125, + "height": 81.72265625, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 272380847, + "version": 898, + "versionNonce": 390850703, + "isDeleted": false + }, + { + "id": "dQlJQhnM0AYclhKPVybPd", + "type": "rectangle", + "x": 546.23046875, + "y": 808.859375, + "width": 92.9453125, + "height": 32.8046875, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 809777537, + "version": 1088, + "versionNonce": 1110436833, + "isDeleted": false + }, + { + "id": "IEH1ricGvlqTHokGxMVjz", + "type": "text", + "x": 653.0703125, + "y": 754.67578125, + "width": 43, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1543380463, + "version": 347, + "versionNonce": 406886127, + "isDeleted": false, + "text": "400", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "MTjY7VNzxpuzcBmIf9I7q", + "type": "text", + "x": 654.796875, + "y": 814.2578125, + "width": 42, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1147598401, + "version": 399, + "versionNonce": 2062275457, + "isDeleted": false, + "text": "240", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "atkM37igZv9d_DsMZG-Xn", + "type": "text", + "x": 575.7578125, + "y": 694.05859375, + "width": 43, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2109302657, + "version": 378, + "versionNonce": 1535233647, + "isDeleted": false, + "text": "400", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "hWAuP965cyUgDthoPFymD", + "type": "rectangle", + "x": 544.65625, + "y": 905.298828125, + "width": 87.26953125, + "height": 38.71484375, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 499751521, + "version": 1183, + "versionNonce": 704511663, + "isDeleted": false + }, + { + "id": "FZI3ByFPWn0m4PILmz4on", + "type": "rectangle", + "x": 543.572265625, + "y": 946.748046875, + "width": 89.89453125, + "height": 22.6640625, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 592454383, + "version": 1284, + "versionNonce": 2078918593, + "isDeleted": false + }, + { + "id": "VxqGuoHiqYxBPerDAFyPJ", + "type": "text", + "x": 645.84765625, + "y": 912.60546875, + "width": 29, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 106276047, + "version": 416, + "versionNonce": 1494081807, + "isDeleted": false, + "text": "160", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "68HEpZfR-XtSn45gAczWN", + "type": "text", + "x": 644.6640625, + "y": 951.078125, + "width": 29, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1660042465, + "version": 434, + "versionNonce": 1879554913, + "isDeleted": false, + "text": "80", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "619FTyDcQgik3IH_ns0Sx", + "type": "text", + "x": 1240.4765625, + "y": 548.5703125, + "width": 147, + "height": 25, + "strokeColor": "#e67700", + "backgroundColor": "#15aabf", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2052194369, + "version": 372, + "versionNonce": 1428371457, + "isDeleted": false, + "text": "640 % x == 0", + "font": "20px Virgil", + "baseline": 18 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/assets/excalidraw/excalidraw-202032823343.excalidraw b/assets/excalidraw/excalidraw-202032823343.excalidraw new file mode 100644 index 0000000..d55f519 --- /dev/null +++ b/assets/excalidraw/excalidraw-202032823343.excalidraw @@ -0,0 +1,1755 @@ +{ + "type": "excalidraw", + "version": 1, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "LbzQoCIFMgKy_IMPWuYKz", + "type": "rectangle", + "x": 590.9765625, + "y": 181.390625, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 670354216, + "version": 52, + "versionNonce": 1909691688, + "isDeleted": false + }, + { + "id": "K2IrNg6AVKmF_TJFpvlFB", + "type": "rectangle", + "x": 648.4814453125, + "y": 279.705078125, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 520710952, + "version": 251, + "versionNonce": 1757306200, + "isDeleted": false + }, + { + "id": "QYTdFCL-FBKIJJNHKMel9", + "type": "rectangle", + "x": 711.5244140625, + "y": 379.126953125, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 907454552, + "version": 239, + "versionNonce": 766638632, + "isDeleted": false + }, + { + "id": "nxUEy9uS2_2U3-fOdJrFv", + "type": "text", + "x": 507.08984375, + "y": 199.734375, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1187344728, + "version": 8, + "versionNonce": 1994724696, + "isDeleted": false, + "text": "3", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "nTemRF5aeLIZbhgxvNJno", + "type": "text", + "x": 511.1689453125, + "y": 301.154296875, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1786974296, + "version": 86, + "versionNonce": 1048172328, + "isDeleted": false, + "text": "2", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "vMv2F5ZPh6G2uk7gbK0Sy", + "type": "text", + "x": 516.0205078125, + "y": 395.158203125, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1890141224, + "version": 58, + "versionNonce": 764793944, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "tGdrDp03cwpcuAvXdaKUX", + "type": "text", + "x": 612.240234375, + "y": 195.3125, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 180588120, + "version": 8, + "versionNonce": 533013288, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "Ns_bVT9wTTdAM2IpL5DL3", + "type": "text", + "x": 677.1474609375, + "y": 301.814453125, + "width": 8, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1961597272, + "version": 75, + "versionNonce": 558366504, + "isDeleted": false, + "text": "7", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "Yk4vdUp3267qw9JaVqBB8", + "type": "text", + "x": 732.4326171875, + "y": 395.759765625, + "width": 12, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1141617960, + "version": 87, + "versionNonce": 27683672, + "isDeleted": false, + "text": "4", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "CRgCRPkF2c5ADhb-MXwBI", + "type": "rectangle", + "x": 881.96875, + "y": 165.87890625, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 860386856, + "version": 298, + "versionNonce": 1083324760, + "isDeleted": false + }, + { + "id": "Obejn-9sN_2_-pwEd7gTD", + "type": "text", + "x": 905.23046875, + "y": 187.10546875, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 347959592, + "version": 52, + "versionNonce": 1942661160, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "LJwWZaCn0n9gF2B0K-qXD", + "type": "rectangle", + "x": 945.0859375, + "y": 166.46875, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 303051048, + "version": 375, + "versionNonce": 28397144, + "isDeleted": false + }, + { + "id": "6gR8boVj0OJMAB5QqDM7A", + "type": "text", + "x": 968.34765625, + "y": 187.6953125, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 633892392, + "version": 52, + "versionNonce": 663970600, + "isDeleted": false, + "text": "3", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "mWnhZ8dECFDn7XaXEBFqj", + "type": "rectangle", + "x": 880.212890625, + "y": 278.908203125, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 248629848, + "version": 391, + "versionNonce": 1478869848, + "isDeleted": false + }, + { + "id": "90MdeSLx7J1OIzMFIKyqZ", + "type": "rectangle", + "x": 943.330078125, + "y": 279.498046875, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1675018072, + "version": 468, + "versionNonce": 2022489640, + "isDeleted": false + }, + { + "id": "zZ4xou6yBmNgQARcthTZG", + "type": "text", + "x": 906.474609375, + "y": 300.134765625, + "width": 8, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1472767784, + "version": 146, + "versionNonce": 843290712, + "isDeleted": false, + "text": "7", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "SIh0DWPKQx6cPk4_muh5D", + "type": "text", + "x": 966.091796875, + "y": 300.724609375, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2038006312, + "version": 146, + "versionNonce": 933411112, + "isDeleted": false, + "text": "2", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "5zyi15GkD3farSacNgYo7", + "type": "rectangle", + "x": 880.96875, + "y": 376.1591796875, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1151824424, + "version": 457, + "versionNonce": 943805480, + "isDeleted": false + }, + { + "id": "pbhQWjssbJaxd8KaPeZlz", + "type": "rectangle", + "x": 944.0859375, + "y": 376.7490234375, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1365124184, + "version": 534, + "versionNonce": 1716216408, + "isDeleted": false + }, + { + "id": "CtI9dIUNofbbIO3ZZCxT_", + "type": "text", + "x": 905.23046875, + "y": 397.3857421875, + "width": 12, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 373746984, + "version": 213, + "versionNonce": 5541672, + "isDeleted": false, + "text": "4", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "PLkGLARMxDxTUHkAyCiuP", + "type": "text", + "x": 972.34765625, + "y": 397.9755859375, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 130537816, + "version": 213, + "versionNonce": 1214076760, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "qXF6LrF3QucKfrw9xnBV3", + "type": "text", + "x": 913.15625, + "y": 458.625, + "width": 66, + "height": 34, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 152653656, + "version": 60, + "versionNonce": 2018011688, + "isDeleted": false, + "text": "fraq", + "font": "28px Cascadia", + "baseline": 27 + }, + { + "id": "7XabK9xnFwaSu0F7pcddW", + "type": "text", + "x": 594.421875, + "y": 461.65625, + "width": 164, + "height": 34, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 221740840, + "version": 157, + "versionNonce": 1937075752, + "isDeleted": false, + "text": "fraq_stack", + "font": "28px Cascadia", + "baseline": 27 + }, + { + "id": "3B0kY4693b_2X0Ryj41Lm", + "type": "rectangle", + "x": 1238.65234375, + "y": 167.255859375, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#495057", + "backgroundColor": "#ced4da", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "roughness": 0, + "opacity": 90, + "seed": 1756643112, + "version": 390, + "versionNonce": 549708584, + "isDeleted": false + }, + { + "id": "uEY7nW9wNi-M_iBzuW-Gt", + "type": "text", + "x": 1157.2763671875, + "y": 279.265625, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1814522664, + "version": 160, + "versionNonce": 99971672, + "isDeleted": false, + "text": "2", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "p9m9an8j_r2qZGV0yEFs1", + "type": "text", + "x": 1162.1279296875, + "y": 373.26953125, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1555446616, + "version": 132, + "versionNonce": 336822056, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "LE0uFPqkM3OH27hqwioOE", + "type": "text", + "x": 1259.916015625, + "y": 181.177734375, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1557446952, + "version": 152, + "versionNonce": 325785688, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "R6pnmt9JOcjtvVzxiahfL", + "type": "rectangle", + "x": 1641.630859375, + "y": 142.185546875, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 619827032, + "version": 328, + "versionNonce": 1306962728, + "isDeleted": false + }, + { + "id": "L6_lFSaJu6mRmwi4_fqEt", + "type": "text", + "x": 1664.892578125, + "y": 163.412109375, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 928477736, + "version": 82, + "versionNonce": 1770715992, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "NoHKe9mXFxByTCCW9OMc9", + "type": "rectangle", + "x": 1704.748046875, + "y": 142.775390625, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 565060696, + "version": 405, + "versionNonce": 87492136, + "isDeleted": false + }, + { + "id": "7kKiSssP_fdIpMHvE6QlP", + "type": "rectangle", + "x": 1639.875, + "y": 255.21484375, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 586090536, + "version": 421, + "versionNonce": 123077976, + "isDeleted": false + }, + { + "id": "FNTwR4uyRWo4XX3A84ni1", + "type": "rectangle", + "x": 1702.9921875, + "y": 255.8046875, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 226684504, + "version": 498, + "versionNonce": 1157209128, + "isDeleted": false + }, + { + "id": "WZyjR38mZUCeYvwqkdXN9", + "type": "text", + "x": 1666.13671875, + "y": 276.44140625, + "width": 8, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 858506024, + "version": 176, + "versionNonce": 1444649560, + "isDeleted": false, + "text": "7", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "e4Vgh0xx9o1sei_ysjVJY", + "type": "text", + "x": 1725.75390625, + "y": 277.03125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 952329048, + "version": 176, + "versionNonce": 1589892904, + "isDeleted": false, + "text": "2", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "Q3pTWR3L3T6EsZCztZbbG", + "type": "rectangle", + "x": 1640.630859375, + "y": 352.4658203125, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1848457304, + "version": 487, + "versionNonce": 1502523944, + "isDeleted": false + }, + { + "id": "dO-jIujKqxP4oyYTby5ln", + "type": "rectangle", + "x": 1703.748046875, + "y": 353.0556640625, + "width": 60.5234375, + "height": 67.453125, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1084430632, + "version": 564, + "versionNonce": 1368056920, + "isDeleted": false + }, + { + "id": "6NvG6N9TsAXYtLKiGjWtF", + "type": "text", + "x": 1664.892578125, + "y": 373.6923828125, + "width": 12, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1637117272, + "version": 243, + "versionNonce": 205925672, + "isDeleted": false, + "text": "4", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "OoMzlVLdoCBPigTeHfObB", + "type": "text", + "x": 1732.009765625, + "y": 374.2822265625, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 37073960, + "version": 243, + "versionNonce": 1683790168, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "9x3hGEzEUBWNhdp3EqZ-6", + "type": "text", + "x": 1672.818359375, + "y": 434.931640625, + "width": 66, + "height": 34, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 743244376, + "version": 90, + "versionNonce": 274090024, + "isDeleted": false, + "text": "fraq", + "font": "28px Cascadia", + "baseline": 27 + }, + { + "id": "aawCqVLYwtKJ-noLoBKox", + "type": "text", + "x": 1240.529296875, + "y": 439.767578125, + "width": 164, + "height": 34, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1287450408, + "version": 231, + "versionNonce": 2100276824, + "isDeleted": false, + "text": "fraq_stack", + "font": "28px Cascadia", + "baseline": 27 + }, + { + "id": "d-dMu5YxSmKP8bFMQyu0u", + "type": "arrow", + "x": 1275.21875, + "y": 186.37109375, + "width": 69.81640625, + "height": 93.92578125, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1070343256, + "version": 120, + "versionNonce": 2125674792, + "isDeleted": false, + "points": [ + [ + 0, + 0 + ], + [ + 69.81640625, + -93.92578125 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "K66VwFpyHO36dH1Eddj0O", + "type": "text", + "x": 1152.697265625, + "y": 177.845703125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 753976920, + "version": 83, + "versionNonce": 1690555736, + "isDeleted": false, + "text": "2", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "jOciBG-rICXvuJWsd2vtx", + "type": "text", + "x": 1727.509765625, + "y": 164.001953125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 855043368, + "version": 83, + "versionNonce": 2077492008, + "isDeleted": false, + "text": "2", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "V4OqOjIKKsKyI2iBOuWeB", + "type": "rectangle", + "x": 596.1787109375, + "y": 280.234375, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1703290712, + "version": 144, + "versionNonce": 1239438632, + "isDeleted": false + }, + { + "id": "r1aXoCmx95EGNAJgU9IaN", + "type": "text", + "x": 617.4423828125, + "y": 294.15625, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2113371224, + "version": 100, + "versionNonce": 1751871528, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "jOonRanfG3YyzyJGpkRzz", + "type": "rectangle", + "x": 598.5107421875, + "y": 379.5, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1442886952, + "version": 174, + "versionNonce": 2054559784, + "isDeleted": false + }, + { + "id": "g-zxFLGlv2jcAXfzb8lJf", + "type": "text", + "x": 619.7744140625, + "y": 393.421875, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1367330856, + "version": 130, + "versionNonce": 659608360, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "3KpAyOOOx_Z_1juWoWma8", + "type": "text", + "x": 680.96484375, + "y": 392.82421875, + "width": 8, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1468587352, + "version": 67, + "versionNonce": 212685400, + "isDeleted": false, + "text": "7", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "GuwnmqyXrEfEFBGLJS9s-", + "type": "rectangle", + "x": 654.193359375, + "y": 378.7421875, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1394369880, + "version": 194, + "versionNonce": 1304299560, + "isDeleted": false + }, + { + "id": "Yk-mSmRZ98I9hom_k1sQm", + "type": "rectangle", + "x": 1298.58251953125, + "y": 264.3798828125, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 767778344, + "version": 322, + "versionNonce": 2138012456, + "isDeleted": false + }, + { + "id": "kc9x4zjeTDa9Qn9ezQ89Y", + "type": "rectangle", + "x": 1361.62548828125, + "y": 363.8017578125, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1937124696, + "version": 310, + "versionNonce": 23593048, + "isDeleted": false + }, + { + "id": "En2NOYvoEciCfIO1wVcSm", + "type": "text", + "x": 1327.24853515625, + "y": 286.4892578125, + "width": 8, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1435447080, + "version": 146, + "versionNonce": 997485608, + "isDeleted": false, + "text": "7", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "zfetf7PlNTsAcEKdJQVG3", + "type": "text", + "x": 1382.53369140625, + "y": 380.4345703125, + "width": 12, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 221186600, + "version": 158, + "versionNonce": 1566877480, + "isDeleted": false, + "text": "4", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "sQCJbo2M-yHm6GOU8XYAn", + "type": "rectangle", + "x": 1246.27978515625, + "y": 264.9091796875, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1260962136, + "version": 215, + "versionNonce": 1679365208, + "isDeleted": false + }, + { + "id": "_QuJks1zzddgqXHe5V9jT", + "type": "text", + "x": 1267.54345703125, + "y": 278.8310546875, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 137811544, + "version": 171, + "versionNonce": 1705298264, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "I1Zqw6vbuAopbaUZ-kH0F", + "type": "rectangle", + "x": 1248.61181640625, + "y": 364.1748046875, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 79317848, + "version": 245, + "versionNonce": 99737176, + "isDeleted": false + }, + { + "id": "NHlcvvwna-HoXEpacoLVs", + "type": "text", + "x": 1269.87548828125, + "y": 378.0966796875, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1795629144, + "version": 201, + "versionNonce": 634671960, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "HY_DKGEPiBWmX2cK7tqSH", + "type": "text", + "x": 1331.06591796875, + "y": 377.4990234375, + "width": 8, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2062636376, + "version": 138, + "versionNonce": 1805658200, + "isDeleted": false, + "text": "7", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "5iAQQxqDeeEFixgHKAD0Q", + "type": "rectangle", + "x": 1304.29443359375, + "y": 363.4169921875, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1736133672, + "version": 265, + "versionNonce": 1008150824, + "isDeleted": false + }, + { + "id": "k-Y5pwK27wOPWj5qLzE2U", + "type": "rectangle", + "x": 590.762451171875, + "y": 631.0869140625, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#495057", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 0, + "opacity": 90, + "seed": 1045409320, + "version": 548, + "versionNonce": 1184220968, + "isDeleted": false + }, + { + "id": "ds_q5IQT7mTZ10_jQuZOd", + "type": "text", + "x": 509.386474609375, + "y": 743.0966796875, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1950050136, + "version": 300, + "versionNonce": 766502184, + "isDeleted": false, + "text": "2", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "PCldp4Xgz43A3ZEeqEL-S", + "type": "text", + "x": 514.238037109375, + "y": 837.1005859375, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 219372072, + "version": 272, + "versionNonce": 831152472, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "8xmiq74N9CnGHAdWgzqa0", + "type": "text", + "x": 612.026123046875, + "y": 645.0087890625, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1507553624, + "version": 292, + "versionNonce": 503757608, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "USsXybqEzVGKlUIqjN7hw", + "type": "text", + "x": 592.639404296875, + "y": 903.5986328125, + "width": 164, + "height": 34, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 713096024, + "version": 371, + "versionNonce": 2065699112, + "isDeleted": false, + "text": "fraq_stack", + "font": "28px Cascadia", + "baseline": 27 + }, + { + "id": "4kCR_3_LfFKDou-fztS_n", + "type": "text", + "x": 504.807373046875, + "y": 641.6767578125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 74960984, + "version": 223, + "versionNonce": 1381215272, + "isDeleted": false, + "text": "2", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "Keg0m0h8wRwxAqhb4GJZZ", + "type": "rectangle", + "x": 650.692626953125, + "y": 728.2109375, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 46925144, + "version": 462, + "versionNonce": 1448863528, + "isDeleted": false + }, + { + "id": "yB_EDHVptcJfempAiIsld", + "type": "rectangle", + "x": 713.735595703125, + "y": 827.6328125, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 275633960, + "version": 450, + "versionNonce": 1201525848, + "isDeleted": false + }, + { + "id": "QS8gGp5OC7kCTwGEUuSeI", + "type": "text", + "x": 679.358642578125, + "y": 750.3203125, + "width": 8, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 952165464, + "version": 286, + "versionNonce": 1479039016, + "isDeleted": false, + "text": "7", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "cprYwwBkVU-K1Pk3O30hU", + "type": "text", + "x": 734.643798828125, + "y": 844.265625, + "width": 12, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1959958872, + "version": 298, + "versionNonce": 347804456, + "isDeleted": false, + "text": "4", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "RRWgPkpUKyxdwGCoZl-OO", + "type": "rectangle", + "x": 598.389892578125, + "y": 728.740234375, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2119500584, + "version": 355, + "versionNonce": 113567832, + "isDeleted": false + }, + { + "id": "GJv3S4ks0UpVFl6CTDjMS", + "type": "text", + "x": 619.653564453125, + "y": 742.662109375, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1176133160, + "version": 311, + "versionNonce": 1807097176, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "4HKt9Sr76l0qepwKuBD36", + "type": "rectangle", + "x": 600.721923828125, + "y": 828.005859375, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1115445544, + "version": 385, + "versionNonce": 1723380312, + "isDeleted": false + }, + { + "id": "NntALLL0e7HTyZ50MFrIc", + "type": "text", + "x": 621.985595703125, + "y": 841.927734375, + "width": 14, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1994847272, + "version": 341, + "versionNonce": 1856022360, + "isDeleted": false, + "text": "5", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "kZgHFWgIysd2XUCrig6kq", + "type": "text", + "x": 683.176025390625, + "y": 841.330078125, + "width": 8, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 424029992, + "version": 278, + "versionNonce": 2143419480, + "isDeleted": false, + "text": "7", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "iMkDVqq-8yFV0STI2_9Dy", + "type": "rectangle", + "x": 656.404541015625, + "y": 827.248046875, + "width": 56.52734375, + "height": 52.84375, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 685094744, + "version": 405, + "versionNonce": 1387042088, + "isDeleted": false + }, + { + "id": "Z7CUbFXXRUKlXNUP-RUD9", + "type": "ellipse", + "x": 642.03125, + "y": 597.06640625, + "width": 55.11328125, + "height": 48.94140625, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 1362693672, + "version": 82, + "versionNonce": 1809163048, + "isDeleted": false + }, + { + "id": "ePVbQtxGAF-2O83zOpNPT", + "type": "text", + "x": 663.587890625, + "y": 609.537109375, + "width": 12, + "height": 24, + "strokeColor": "#c92a2a", + "backgroundColor": "#e64980", + "fillStyle": "hachure", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 1790941992, + "version": 4, + "versionNonce": 1459161944, + "isDeleted": false, + "text": "1", + "font": "20px Cascadia", + "baseline": 19 + }, + { + "id": "SmQPUGWFlOWJt9oH3OmVv", + "type": "ellipse", + "x": 665.111328125, + "y": 691.009765625, + "width": 55.11328125, + "height": 48.94140625, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 1128777304, + "version": 170, + "versionNonce": 1606810152, + "isDeleted": false + }, + { + "id": "Mkw04C_SZdaY1mkGZSnZg", + "type": "ellipse", + "x": 594.716796875, + "y": 688.591796875, + "width": 55.11328125, + "height": 48.94140625, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 639957032, + "version": 228, + "versionNonce": 1759754584, + "isDeleted": false + }, + { + "id": "ehLNVqNqr7O4yvxbhijdU", + "type": "text", + "x": 616.2734375, + "y": 701.0625, + "width": 12, + "height": 24, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 861178408, + "version": 4, + "versionNonce": 1101958232, + "isDeleted": false, + "text": "3", + "font": "20px Cascadia", + "baseline": 19 + }, + { + "id": "yyWTn6tjovntdC5Nm2-1I", + "type": "text", + "x": 686.66796875, + "y": 703.48046875, + "width": 12, + "height": 24, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 291356760, + "version": 5, + "versionNonce": 1212652584, + "isDeleted": false, + "text": "2", + "font": "20px Cascadia", + "baseline": 19 + }, + { + "id": "pZJpS96HMoEbCFBfHwf7D", + "type": "diamond", + "x": 618.59375, + "y": 710.58203125, + "width": 0, + "height": 0, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 1991088728, + "version": 3, + "versionNonce": 0, + "isDeleted": false + }, + { + "id": "pA624wxGoX-o-zh5n7SN9", + "type": "ellipse", + "x": 666.4013671875, + "y": 791.140625, + "width": 55.11328125, + "height": 48.94140625, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 1210506584, + "version": 218, + "versionNonce": 1657927000, + "isDeleted": false + }, + { + "id": "dqnsCl8m6kq9ayX25syoc", + "type": "ellipse", + "x": 596.0068359375, + "y": 788.72265625, + "width": 55.11328125, + "height": 48.94140625, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 1480737832, + "version": 276, + "versionNonce": 1668590632, + "isDeleted": false + }, + { + "id": "GrJuPSH5gNDgGr1RMZmn1", + "type": "ellipse", + "x": 730.716796875, + "y": 788.806640625, + "width": 55.11328125, + "height": 48.94140625, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 2131884072, + "version": 131, + "versionNonce": 2073486120, + "isDeleted": false + }, + { + "id": "T6tfG-PpTzVCHVY_gKNUg", + "type": "text", + "x": 752.2734375, + "y": 801.27734375, + "width": 12, + "height": 24, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 1612652632, + "version": 3, + "versionNonce": 220442920, + "isDeleted": false, + "text": "4", + "font": "20px Cascadia", + "baseline": 19 + }, + { + "id": "Gcd9QGhJzPbcXWISdWY3U", + "type": "text", + "x": 687.9580078125, + "y": 803.611328125, + "width": 12, + "height": 24, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 1387757352, + "version": 54, + "versionNonce": 1726274088, + "isDeleted": false, + "text": "5", + "font": "20px Cascadia", + "baseline": 19 + }, + { + "id": "kOXre1qx1mgGX8UrEGhm6", + "type": "text", + "x": 617.5634765625, + "y": 801.193359375, + "width": 12, + "height": 24, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 4, + "roughness": 1, + "opacity": 90, + "seed": 1403787048, + "version": 2, + "versionNonce": 505565016, + "isDeleted": false, + "text": "6", + "font": "20px Cascadia", + "baseline": 19 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/assets/excalidraw/excalidraw-2020329104236.excalidraw b/assets/excalidraw/excalidraw-2020329104236.excalidraw new file mode 100644 index 0000000..63ea24d --- /dev/null +++ b/assets/excalidraw/excalidraw-2020329104236.excalidraw @@ -0,0 +1,1828 @@ +{ + "type": "excalidraw", + "version": 1, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "J0F7tcAdn8C8XbPSjZSFI", + "type": "rectangle", + "x": 646.0546875, + "y": 137.48828125, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 948615416, + "version": 79, + "versionNonce": 30526600, + "isDeleted": false + }, + { + "id": "vebMMLdSQ-VPDx8jvoxxD", + "type": "text", + "x": 671.65234375, + "y": 151.302734375, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1505680264, + "version": 3, + "versionNonce": 1255440632, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "MXyOzbh0Np8BoY-PGVtNQ", + "type": "rectangle", + "x": 644.62890625, + "y": 246.224609375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1410009224, + "version": 129, + "versionNonce": 964408312, + "isDeleted": false + }, + { + "id": "KtcWK0hPf0y_aVKvioR7b", + "type": "text", + "x": 670.2265625, + "y": 260.0390625, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 208511992, + "version": 53, + "versionNonce": 1950590856, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "sP9jLFg-ZmbUYY3ych8Zl", + "type": "rectangle", + "x": 645.16015625, + "y": 190.107421875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 991893496, + "version": 184, + "versionNonce": 250465016, + "isDeleted": false + }, + { + "id": "gPp5itGY30GrCRbeG-ai0", + "type": "rectangle", + "x": 702.05859375, + "y": 191.548828125, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1860133768, + "version": 265, + "versionNonce": 533802888, + "isDeleted": false + }, + { + "id": "HyW71wRvdeLiZBl3mtKvb", + "type": "rectangle", + "x": 757.734375, + "y": 190.580078125, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2032972280, + "version": 259, + "versionNonce": 754519544, + "isDeleted": false + }, + { + "id": "vYu5IRfv5WeizK9SZFMTF", + "type": "rectangle", + "x": 702.98046875, + "y": 137.892578125, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 719288312, + "version": 230, + "versionNonce": 1921225720, + "isDeleted": false + }, + { + "id": "gAEej_dBgJCL2mYF-rAH5", + "type": "rectangle", + "x": 699.31640625, + "y": 243.380859375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1341883528, + "version": 278, + "versionNonce": 577921272, + "isDeleted": false + }, + { + "id": "mw7YO5Z5c2HnWugEaeywd", + "type": "rectangle", + "x": 758.23828125, + "y": 139.251953125, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1468186504, + "version": 140, + "versionNonce": 1241394568, + "isDeleted": false + }, + { + "id": "RAQ4RhpzjdjX-efv5-RVo", + "type": "text", + "x": 783.8359375, + "y": 153.06640625, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1722866936, + "version": 64, + "versionNonce": 2120961784, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "NGdN5a5m0wNpfkmhKAQIH", + "type": "rectangle", + "x": 756.30078125, + "y": 244.771484375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 792724472, + "version": 136, + "versionNonce": 139096824, + "isDeleted": false + }, + { + "id": "28kiIMrd9UD5X6n76aI0U", + "type": "text", + "x": 781.8984375, + "y": 258.5859375, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1739347848, + "version": 60, + "versionNonce": 1308907656, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "WTECX8TRfvMy7za2hdJGU", + "type": "rectangle", + "x": 642.1640625, + "y": 555.2314453125, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 401166328, + "version": 208, + "versionNonce": 1673430008, + "isDeleted": false + }, + { + "id": "uP5c2iUL4zcuTz5Vnmnzq", + "type": "text", + "x": 667.76171875, + "y": 569.0458984375, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 839764872, + "version": 128, + "versionNonce": 542496648, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "Se0aGZQfNYdZJQg18ncmi", + "type": "rectangle", + "x": 640.73828125, + "y": 663.9677734375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 388796664, + "version": 253, + "versionNonce": 440091896, + "isDeleted": false + }, + { + "id": "TjCS-T6VdtMHmKm8nN4Ct", + "type": "text", + "x": 666.3359375, + "y": 677.7822265625, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 744168072, + "version": 177, + "versionNonce": 767665800, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "BUKLyIDmkkjAJfYWCpDOp", + "type": "rectangle", + "x": 641.26953125, + "y": 607.8505859375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 778023416, + "version": 304, + "versionNonce": 1165485560, + "isDeleted": false + }, + { + "id": "xS6C5dx2CrSC6KEVhkxGJ", + "type": "rectangle", + "x": 698.16796875, + "y": 609.2919921875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1655445640, + "version": 395, + "versionNonce": 1289725064, + "isDeleted": false + }, + { + "id": "pp3iVnwzj8nFO0c5TGloi", + "type": "rectangle", + "x": 753.84375, + "y": 608.3232421875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 926962936, + "version": 379, + "versionNonce": 1934952696, + "isDeleted": false + }, + { + "id": "j3ScM1DpeFyLn2wJqyjvT", + "type": "rectangle", + "x": 699.08984375, + "y": 555.6357421875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1349617032, + "version": 352, + "versionNonce": 1589193208, + "isDeleted": false + }, + { + "id": "YfFnLVoL9x-dhedqvJeyd", + "type": "rectangle", + "x": 695.42578125, + "y": 661.1240234375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 371185656, + "version": 400, + "versionNonce": 1480925320, + "isDeleted": false + }, + { + "id": "p4QyU5aZIDNfIthvOh_Ga", + "type": "rectangle", + "x": 754.34765625, + "y": 556.9951171875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 724701832, + "version": 264, + "versionNonce": 1600965880, + "isDeleted": false + }, + { + "id": "JOStt5_AO-0Rj13NgRQ7U", + "type": "text", + "x": 779.9453125, + "y": 570.8095703125, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1720990200, + "version": 188, + "versionNonce": 877436552, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "_lU2GxViCXuhtBpPSuqMW", + "type": "rectangle", + "x": 752.41015625, + "y": 662.5146484375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 660504968, + "version": 260, + "versionNonce": 2067295736, + "isDeleted": false + }, + { + "id": "Dv4BKgHqTk8Tv6FsD42xg", + "type": "text", + "x": 778.0078125, + "y": 676.3291015625, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1693072120, + "version": 181, + "versionNonce": 1162317192, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "ySu2FIEtLU-WxQaJy9VcC", + "type": "text", + "x": 723.078125, + "y": 151.70703125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1067448312, + "version": 5, + "versionNonce": 498557064, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "dgnvFYOuigaDRUwRWftgw", + "type": "text", + "x": 665.2578125, + "y": 203.921875, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1677312648, + "version": 6, + "versionNonce": 754580616, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "cIXMCS2lzwUSotv4mspbB", + "type": "text", + "x": 722.15625, + "y": 205.36328125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 295604360, + "version": 8, + "versionNonce": 890761208, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "tVCKGiR9U3d47zpli35H4", + "type": "text", + "x": 777.83203125, + "y": 204.39453125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1438985720, + "version": 6, + "versionNonce": 1016671112, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "j6t_hGHGOu2H5r5Mrt4dJ", + "type": "text", + "x": 719.4140625, + "y": 257.1953125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1164288504, + "version": 5, + "versionNonce": 2013925256, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "i7hYkz31L0_lF6mRrRFsx", + "type": "text", + "x": 718.265625, + "y": 623.1064453125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1179023096, + "version": 56, + "versionNonce": 209864584, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "wNQbAeDW6QVpybb3Yk7Qk", + "type": "text", + "x": 720.6875, + "y": 569.4501953125, + "width": 12, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 67192968, + "version": 49, + "versionNonce": 1637973496, + "isDeleted": false, + "text": "-1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "cmlvHkwL2AUkG-Btwx1VZ", + "type": "text", + "x": 662.8671875, + "y": 621.6650390625, + "width": 12, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 390278280, + "version": 50, + "versionNonce": 565673352, + "isDeleted": false, + "text": "-1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "FHqASiARSD5by1pevvX7B", + "type": "text", + "x": 775.44140625, + "y": 622.1376953125, + "width": 12, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 910305160, + "version": 50, + "versionNonce": 363790072, + "isDeleted": false, + "text": "-1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "l7lkIZvIK_aqTE9PgrCPe", + "type": "text", + "x": 717.0234375, + "y": 674.9384765625, + "width": 12, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1894017784, + "version": 49, + "versionNonce": 536775816, + "isDeleted": false, + "text": "-1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "stD7H5GIR0_eD_QfCvyM9", + "type": "rectangle", + "x": 634.369140625, + "y": 773.3681640625, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1957100024, + "version": 265, + "versionNonce": 166230408, + "isDeleted": false + }, + { + "id": "fvAysWSOMBWckQ-mZJW1n", + "type": "text", + "x": 659.966796875, + "y": 787.1826171875, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1222099336, + "version": 185, + "versionNonce": 1109115640, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "9IUcf_Xn4HxCEx9SkaJVz", + "type": "rectangle", + "x": 632.943359375, + "y": 882.1044921875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 581237496, + "version": 310, + "versionNonce": 1353713800, + "isDeleted": false + }, + { + "id": "RUJh1lGjaquS6FZWcG4ds", + "type": "text", + "x": 658.541015625, + "y": 895.9189453125, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 41308296, + "version": 234, + "versionNonce": 1486398456, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "7IDkFwHzJc4y336SOlG8C", + "type": "rectangle", + "x": 633.474609375, + "y": 825.9873046875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1127930872, + "version": 361, + "versionNonce": 363706248, + "isDeleted": false + }, + { + "id": "lut3XE6M6hLAvifMRvaUW", + "type": "rectangle", + "x": 690.373046875, + "y": 827.4287109375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 133287560, + "version": 455, + "versionNonce": 575276536, + "isDeleted": false + }, + { + "id": "vkdR3alWin6QLeCH2KLO_", + "type": "rectangle", + "x": 746.048828125, + "y": 826.4599609375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1446231800, + "version": 436, + "versionNonce": 247694472, + "isDeleted": false + }, + { + "id": "YQrqybjUistkC-JYUwajq", + "type": "rectangle", + "x": 691.294921875, + "y": 773.7724609375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 542187512, + "version": 409, + "versionNonce": 1005695880, + "isDeleted": false + }, + { + "id": "-ZryvBSAjL_sk3uaznl1p", + "type": "rectangle", + "x": 687.630859375, + "y": 879.2607421875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 737299080, + "version": 457, + "versionNonce": 830163448, + "isDeleted": false + }, + { + "id": "0sbjFt5kwUugAmNcOQS8q", + "type": "rectangle", + "x": 746.552734375, + "y": 775.1318359375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 977306360, + "version": 321, + "versionNonce": 423166088, + "isDeleted": false + }, + { + "id": "FYtEPWQ-vWp3QpvP5Kgz7", + "type": "text", + "x": 772.150390625, + "y": 788.9462890625, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1782007944, + "version": 245, + "versionNonce": 1933257720, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "RX-Hbwe0JQkB9vn1HJQgi", + "type": "rectangle", + "x": 744.615234375, + "y": 880.6513671875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1847944184, + "version": 317, + "versionNonce": 1381478280, + "isDeleted": false + }, + { + "id": "4n3sZBa9tXQ3jqSzPU_tC", + "type": "text", + "x": 770.212890625, + "y": 894.4658203125, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2085418888, + "version": 238, + "versionNonce": 1675816184, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "pa4U0CGEdn4jKMdmov70x", + "type": "text", + "x": 712.892578125, + "y": 787.5869140625, + "width": 12, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1037354632, + "version": 106, + "versionNonce": 1908965000, + "isDeleted": false, + "text": "-1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "AZUZfAfXwqqk_bJ1sRVbQ", + "type": "text", + "x": 655.072265625, + "y": 839.8017578125, + "width": 12, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1269220856, + "version": 107, + "versionNonce": 1273662968, + "isDeleted": false, + "text": "-1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "15e8vM3RzpIdFBkO7cJWl", + "type": "text", + "x": 767.646484375, + "y": 840.2744140625, + "width": 12, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 998946184, + "version": 107, + "versionNonce": 675769736, + "isDeleted": false, + "text": "-1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "hoHT7sHhdrlXV97RPEJp4", + "type": "text", + "x": 709.228515625, + "y": 893.0751953125, + "width": 12, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 533415672, + "version": 106, + "versionNonce": 1951796984, + "isDeleted": false, + "text": "-1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "uombKPR26crWbStUScxYZ", + "type": "text", + "x": 711.970703125, + "y": 841.2431640625, + "width": 12, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1959142648, + "version": 111, + "versionNonce": 789609608, + "isDeleted": false, + "text": "-1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "tUzAJuArJJw2JROkbpds7", + "type": "rectangle", + "x": 646.6328125, + "y": 342.0634765625, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 239076600, + "version": 146, + "versionNonce": 1003207160, + "isDeleted": false + }, + { + "id": "kl0oAgWTZkc1lQ_m3m7H2", + "type": "text", + "x": 672.23046875, + "y": 355.8779296875, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2036917896, + "version": 70, + "versionNonce": 56557960, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "78DgDt_0S-LJ2g_BKFvlP", + "type": "rectangle", + "x": 645.20703125, + "y": 450.7998046875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1524934136, + "version": 196, + "versionNonce": 1961718776, + "isDeleted": false + }, + { + "id": "j3PXkrN2_NTs4sOBHbFZN", + "type": "text", + "x": 670.8046875, + "y": 464.6142578125, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 798676360, + "version": 120, + "versionNonce": 835577736, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "RKSt4cULQrMWgZbjfqmzC", + "type": "rectangle", + "x": 645.73828125, + "y": 394.6826171875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1280124664, + "version": 245, + "versionNonce": 882036984, + "isDeleted": false + }, + { + "id": "A1zfYTQM6OZOdoCS_4vHa", + "type": "rectangle", + "x": 702.63671875, + "y": 396.1240234375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1064899464, + "version": 326, + "versionNonce": 126634376, + "isDeleted": false + }, + { + "id": "hFK-KoKLSqNFNkACGZ2Ua", + "type": "rectangle", + "x": 758.3125, + "y": 395.1552734375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2009459192, + "version": 320, + "versionNonce": 875811832, + "isDeleted": false + }, + { + "id": "6BTE_vsRFxRjh1MxY8pR-", + "type": "rectangle", + "x": 703.55859375, + "y": 342.4677734375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 226254984, + "version": 291, + "versionNonce": 1100174984, + "isDeleted": false + }, + { + "id": "EWB5a6Thz1ozNFmWAtHWj", + "type": "rectangle", + "x": 699.89453125, + "y": 447.9560546875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 657395592, + "version": 339, + "versionNonce": 1761035656, + "isDeleted": false + }, + { + "id": "6WuRp2pAM4WZyltAhdagw", + "type": "rectangle", + "x": 758.81640625, + "y": 343.8271484375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1206201848, + "version": 207, + "versionNonce": 521053064, + "isDeleted": false + }, + { + "id": "lTVfYpajl-wGVJYsYiZ8m", + "type": "text", + "x": 784.4140625, + "y": 357.6416015625, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1992740232, + "version": 131, + "versionNonce": 423966968, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "FrO6tBfDk0KizoNRP5YJV", + "type": "rectangle", + "x": 756.87890625, + "y": 449.3466796875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1476177656, + "version": 203, + "versionNonce": 1787775224, + "isDeleted": false + }, + { + "id": "dDuO_VILnt_ZXdBqu2sOn", + "type": "text", + "x": 782.4765625, + "y": 463.1611328125, + "width": 4, + "height": 25, + "strokeColor": "#c92a2a", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 916708488, + "version": 127, + "versionNonce": 1067442824, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "8wU76lx08q6bzlzEDd11X", + "type": "text", + "x": 723.65625, + "y": 356.2822265625, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1534857208, + "version": 66, + "versionNonce": 1257404920, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "By3LCqtcCE2NDcpOiHYaJ", + "type": "text", + "x": 665.8359375, + "y": 408.4970703125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 906431368, + "version": 67, + "versionNonce": 1862549896, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "AtF-kp9rHqi_T0WnEybTn", + "type": "text", + "x": 722.734375, + "y": 409.9384765625, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1516720376, + "version": 69, + "versionNonce": 595622648, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "nGs-lJuACmp_CbSYQpQDX", + "type": "text", + "x": 778.41015625, + "y": 408.9697265625, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1596742280, + "version": 67, + "versionNonce": 789762184, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "i4r5kooCcLS7e6BVL5fpp", + "type": "text", + "x": 719.9921875, + "y": 461.7705078125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1010987512, + "version": 66, + "versionNonce": 480664568, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "95_txRNQSFOXN8JYYFEqS", + "type": "rectangle", + "x": 1183.14453125, + "y": 124.6533203125, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 637522936, + "version": 129, + "versionNonce": 198096632, + "isDeleted": false + }, + { + "id": "kSo0d3VzVb9yBMaDFjuI9", + "type": "text", + "x": 1208.7421875, + "y": 138.4677734375, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 428967816, + "version": 53, + "versionNonce": 1725322376, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "E6lRG9ezUfXyXzEXokjzo", + "type": "rectangle", + "x": 1181.71875, + "y": 233.3896484375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 668112120, + "version": 179, + "versionNonce": 1473058808, + "isDeleted": false + }, + { + "id": "fxXhtIrof1QYllyiRJQJF", + "type": "text", + "x": 1207.31640625, + "y": 247.2041015625, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1678202504, + "version": 103, + "versionNonce": 690652040, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "f2tisUVwJhZDbK1aEI2xA", + "type": "rectangle", + "x": 1182.25, + "y": 177.2724609375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1134900728, + "version": 234, + "versionNonce": 928471288, + "isDeleted": false + }, + { + "id": "ZlHxtH_LZZmxmofyIGezW", + "type": "rectangle", + "x": 1239.1484375, + "y": 178.7138671875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#343a40", + "backgroundColor": "#12b886", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 293164168, + "version": 327, + "versionNonce": 2133647352, + "isDeleted": false + }, + { + "id": "Q5dVGkFISZPOi1ms2mztj", + "type": "rectangle", + "x": 1294.82421875, + "y": 177.7451171875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 314857720, + "version": 309, + "versionNonce": 1810193400, + "isDeleted": false + }, + { + "id": "llrX0AkJX3y_IGxFIXh1f", + "type": "rectangle", + "x": 1240.0703125, + "y": 125.0576171875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 150487432, + "version": 280, + "versionNonce": 1904168584, + "isDeleted": false + }, + { + "id": "Z-DVcAtvMrVufsaa6ECh-", + "type": "rectangle", + "x": 1236.40625, + "y": 230.5458984375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1787220104, + "version": 328, + "versionNonce": 681852296, + "isDeleted": false + }, + { + "id": "2ZQkRUt2s_OP2hBNecpcV", + "type": "rectangle", + "x": 1295.328125, + "y": 126.4169921875, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1689525496, + "version": 190, + "versionNonce": 1606131704, + "isDeleted": false + }, + { + "id": "7H9hdlZBI1P74sw0hOrx5", + "type": "text", + "x": 1320.92578125, + "y": 140.2314453125, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 98717320, + "version": 114, + "versionNonce": 1128823688, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "7N86neKsg29LgzzHcAkxN", + "type": "rectangle", + "x": 1293.390625, + "y": 231.9365234375, + "width": 55.1953125, + "height": 52.62890625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 2065022456, + "version": 186, + "versionNonce": 1345642744, + "isDeleted": false + }, + { + "id": "RLYlisDMCS4rqq4hg59zt", + "type": "text", + "x": 1318.98828125, + "y": 245.7509765625, + "width": 4, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 950484360, + "version": 110, + "versionNonce": 582994568, + "isDeleted": false, + "text": "1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "68Iraw2m4xzvtedMEI_3X", + "type": "text", + "x": 1260.16796875, + "y": 138.8720703125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1989885688, + "version": 55, + "versionNonce": 1105068536, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "mkAM-iUXJUnPIZsb83AMw", + "type": "text", + "x": 1202.34765625, + "y": 191.0869140625, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1123437704, + "version": 56, + "versionNonce": 1416968584, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "qsGCYXbCDot8dYkuInrAL", + "type": "text", + "x": 1259.24609375, + "y": 192.5283203125, + "width": 15, + "height": 25, + "strokeColor": "#087f5b", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1204330488, + "version": 74, + "versionNonce": 1775631240, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "jPeJlG7ipmYq4PTs5eh_L", + "type": "text", + "x": 1314.921875, + "y": 191.5595703125, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1854634888, + "version": 56, + "versionNonce": 732219528, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "akei9oacRDJAy9H00lV3S", + "type": "text", + "x": 1256.50390625, + "y": 244.3603515625, + "width": 15, + "height": 25, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 782360824, + "version": 55, + "versionNonce": 1682957304, + "isDeleted": false, + "text": "0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "mMHHQhHwNlX_cKXmWd0e0", + "type": "text", + "x": 835.546875, + "y": 411.90234375, + "width": 82, + "height": 25, + "strokeColor": "#862e9c", + "backgroundColor": "#12b886", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 1382851976, + "version": 100, + "versionNonce": 1722638072, + "isDeleted": false, + "text": "steps 0", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "nVkM6VOPAC_wGDAyn9HtF", + "type": "text", + "x": 842.375, + "y": 625.1328125, + "width": 70, + "height": 25, + "strokeColor": "#862e9c", + "backgroundColor": "#12b886", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 711154680, + "version": 146, + "versionNonce": 1690645896, + "isDeleted": false, + "text": "steps 1", + "font": "20px Virgil", + "baseline": 18 + }, + { + "id": "deMedVE410lO1KhWhNPg2", + "type": "text", + "x": 834.39453125, + "y": 847.1953125, + "width": 82, + "height": 25, + "strokeColor": "#862e9c", + "backgroundColor": "#12b886", + "fillStyle": "hachure", + "strokeWidth": 1, + "roughness": 1, + "opacity": 100, + "seed": 824330744, + "version": 189, + "versionNonce": 683944184, + "isDeleted": false, + "text": "steps 2", + "font": "20px Virgil", + "baseline": 18 + } + ], + "appState": { + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/assets/gongzhonghao.jpeg b/assets/gongzhonghao.jpeg new file mode 100644 index 0000000..58911a4 Binary files /dev/null and b/assets/gongzhonghao.jpeg differ diff --git a/assets/leetcode-zhihu.jpg b/assets/leetcode-zhihu.jpg new file mode 100644 index 0000000..162732c Binary files /dev/null and b/assets/leetcode-zhihu.jpg differ diff --git a/assets/leetcode.jpeg b/assets/leetcode.jpeg new file mode 100644 index 0000000..d3982d0 Binary files /dev/null and b/assets/leetcode.jpeg differ diff --git a/assets/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.drawio b/assets/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.drawio new file mode 100644 index 0000000..09d0d6c --- /dev/null +++ b/assets/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/assets/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.png b/assets/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.png new file mode 100644 index 0000000..713da19 Binary files /dev/null and b/assets/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.png differ diff --git a/assets/problems/105.index_explain.jpg b/assets/problems/105.index_explain.jpg new file mode 100644 index 0000000..3823df0 Binary files /dev/null and b/assets/problems/105.index_explain.jpg differ diff --git a/assets/problems/11.container-with-most-water-question.jpg b/assets/problems/11.container-with-most-water-question.jpg new file mode 100644 index 0000000..7661efe Binary files /dev/null and b/assets/problems/11.container-with-most-water-question.jpg differ diff --git a/assets/problems/11.container-with-most-water.png b/assets/problems/11.container-with-most-water.png new file mode 100644 index 0000000..6769bb8 Binary files /dev/null and b/assets/problems/11.container-with-most-water.png differ diff --git a/assets/problems/1168.optimize-water-distribution-in-a-village-1.png b/assets/problems/1168.optimize-water-distribution-in-a-village-1.png new file mode 100644 index 0000000..0f45347 Binary files /dev/null and b/assets/problems/1168.optimize-water-distribution-in-a-village-1.png differ diff --git a/assets/problems/1168.optimize-water-distribution-in-a-village-example1.png b/assets/problems/1168.optimize-water-distribution-in-a-village-example1.png new file mode 100644 index 0000000..63dfa80 Binary files /dev/null and b/assets/problems/1168.optimize-water-distribution-in-a-village-example1.png differ diff --git a/assets/problems/121.best-time-to-buy-and-sell-stock.jpg b/assets/problems/121.best-time-to-buy-and-sell-stock.jpg new file mode 100644 index 0000000..0158658 Binary files /dev/null and b/assets/problems/121.best-time-to-buy-and-sell-stock.jpg differ diff --git a/assets/problems/121.best-time-to-buy-and-sell-stock.png b/assets/problems/121.best-time-to-buy-and-sell-stock.png new file mode 100644 index 0000000..830b9b9 Binary files /dev/null and b/assets/problems/121.best-time-to-buy-and-sell-stock.png differ diff --git a/assets/problems/122.best-time-to-buy-and-sell-stock-ii.png b/assets/problems/122.best-time-to-buy-and-sell-stock-ii.png new file mode 100644 index 0000000..d389d65 Binary files /dev/null and b/assets/problems/122.best-time-to-buy-and-sell-stock-ii.png differ diff --git a/assets/problems/124.binary-tree-maximum-path-sum-1.jpg b/assets/problems/124.binary-tree-maximum-path-sum-1.jpg new file mode 100644 index 0000000..a1875ce Binary files /dev/null and b/assets/problems/124.binary-tree-maximum-path-sum-1.jpg differ diff --git a/assets/problems/124.binary-tree-maximum-path-sum.jpg b/assets/problems/124.binary-tree-maximum-path-sum.jpg new file mode 100644 index 0000000..c18e90c Binary files /dev/null and b/assets/problems/124.binary-tree-maximum-path-sum.jpg differ diff --git a/assets/problems/125.valid-palindrome-1.png b/assets/problems/125.valid-palindrome-1.png new file mode 100644 index 0000000..85a5f6c Binary files /dev/null and b/assets/problems/125.valid-palindrome-1.png differ diff --git a/assets/problems/125.valid-palindrome-2.png b/assets/problems/125.valid-palindrome-2.png new file mode 100644 index 0000000..dc7c845 Binary files /dev/null and b/assets/problems/125.valid-palindrome-2.png differ diff --git a/assets/problems/129.sum-root-to-leaf-numbers-1.jpg b/assets/problems/129.sum-root-to-leaf-numbers-1.jpg new file mode 100644 index 0000000..859ffc3 Binary files /dev/null and b/assets/problems/129.sum-root-to-leaf-numbers-1.jpg differ diff --git a/assets/problems/129.sum-root-to-leaf-numbers-2.jpg b/assets/problems/129.sum-root-to-leaf-numbers-2.jpg new file mode 100644 index 0000000..86f062d Binary files /dev/null and b/assets/problems/129.sum-root-to-leaf-numbers-2.jpg differ diff --git a/assets/problems/130.surrounded-regions-1.jpg b/assets/problems/130.surrounded-regions-1.jpg new file mode 100644 index 0000000..fe0a9be Binary files /dev/null and b/assets/problems/130.surrounded-regions-1.jpg differ diff --git a/assets/problems/130.surrounded-regions-2.jpg b/assets/problems/130.surrounded-regions-2.jpg new file mode 100644 index 0000000..da21864 Binary files /dev/null and b/assets/problems/130.surrounded-regions-2.jpg differ diff --git a/assets/problems/139.word-break-1.png b/assets/problems/139.word-break-1.png new file mode 100644 index 0000000..3e2c300 Binary files /dev/null and b/assets/problems/139.word-break-1.png differ diff --git a/assets/problems/139.word-break-2.png b/assets/problems/139.word-break-2.png new file mode 100644 index 0000000..0ca76c6 Binary files /dev/null and b/assets/problems/139.word-break-2.png differ diff --git a/assets/problems/139.word-break-3.png b/assets/problems/139.word-break-3.png new file mode 100644 index 0000000..30850c8 Binary files /dev/null and b/assets/problems/139.word-break-3.png differ diff --git a/assets/problems/139.word-break-4.png b/assets/problems/139.word-break-4.png new file mode 100644 index 0000000..2dc3615 Binary files /dev/null and b/assets/problems/139.word-break-4.png differ diff --git a/assets/problems/139.word-break-5.png b/assets/problems/139.word-break-5.png new file mode 100644 index 0000000..130c96b Binary files /dev/null and b/assets/problems/139.word-break-5.png differ diff --git a/assets/problems/139.word-break-6.png b/assets/problems/139.word-break-6.png new file mode 100644 index 0000000..d8c0aa3 Binary files /dev/null and b/assets/problems/139.word-break-6.png differ diff --git a/assets/problems/15.3-sum.png b/assets/problems/15.3-sum.png new file mode 100644 index 0000000..bcb59bb Binary files /dev/null and b/assets/problems/15.3-sum.png differ diff --git a/assets/problems/152.maximum-product-subarray.png b/assets/problems/152.maximum-product-subarray.png new file mode 100644 index 0000000..fc8d81c Binary files /dev/null and b/assets/problems/152.maximum-product-subarray.png differ diff --git a/assets/problems/155.min-stack-1.png b/assets/problems/155.min-stack-1.png new file mode 100644 index 0000000..f254e1d Binary files /dev/null and b/assets/problems/155.min-stack-1.png differ diff --git a/assets/problems/155.min-stack-2.png b/assets/problems/155.min-stack-2.png new file mode 100644 index 0000000..610a971 Binary files /dev/null and b/assets/problems/155.min-stack-2.png differ diff --git a/assets/problems/155.min-stack-3.png b/assets/problems/155.min-stack-3.png new file mode 100644 index 0000000..c84ef98 Binary files /dev/null and b/assets/problems/155.min-stack-3.png differ diff --git a/assets/problems/169.majority-element.png b/assets/problems/169.majority-element.png new file mode 100644 index 0000000..289315a Binary files /dev/null and b/assets/problems/169.majority-element.png differ diff --git a/assets/problems/172.factorial-trailing-zeroes-1.png b/assets/problems/172.factorial-trailing-zeroes-1.png new file mode 100644 index 0000000..29735f2 Binary files /dev/null and b/assets/problems/172.factorial-trailing-zeroes-1.png differ diff --git a/assets/problems/172.factorial-trailing-zeroes-2.png b/assets/problems/172.factorial-trailing-zeroes-2.png new file mode 100644 index 0000000..5bc35e6 Binary files /dev/null and b/assets/problems/172.factorial-trailing-zeroes-2.png differ diff --git a/assets/problems/172.factorial-trailing-zeroes-3.png b/assets/problems/172.factorial-trailing-zeroes-3.png new file mode 100644 index 0000000..9eaa834 Binary files /dev/null and b/assets/problems/172.factorial-trailing-zeroes-3.png differ diff --git a/assets/problems/191.number-of-1-bits.png b/assets/problems/191.number-of-1-bits.png new file mode 100644 index 0000000..85a7804 Binary files /dev/null and b/assets/problems/191.number-of-1-bits.png differ diff --git a/assets/problems/198.house-robber.png b/assets/problems/198.house-robber.png new file mode 100644 index 0000000..d9bbd06 Binary files /dev/null and b/assets/problems/198.house-robber.png differ diff --git a/assets/problems/200.number-of-islands.jpg b/assets/problems/200.number-of-islands.jpg new file mode 100644 index 0000000..ce4fc58 Binary files /dev/null and b/assets/problems/200.number-of-islands.jpg differ diff --git a/assets/problems/208.implement-trie-prefix-tree-1.png b/assets/problems/208.implement-trie-prefix-tree-1.png new file mode 100644 index 0000000..573565a Binary files /dev/null and b/assets/problems/208.implement-trie-prefix-tree-1.png differ diff --git a/assets/problems/209.minimum-size-subarray-sum.png b/assets/problems/209.minimum-size-subarray-sum.png new file mode 100644 index 0000000..78df70b Binary files /dev/null and b/assets/problems/209.minimum-size-subarray-sum.png differ diff --git a/assets/problems/215.kth-largest-element-in-an-array-heap.jpg b/assets/problems/215.kth-largest-element-in-an-array-heap.jpg new file mode 100644 index 0000000..ffdbe7a Binary files /dev/null and b/assets/problems/215.kth-largest-element-in-an-array-heap.jpg differ diff --git a/assets/problems/215.kth-largest-element-in-an-array-quick-select.jpg b/assets/problems/215.kth-largest-element-in-an-array-quick-select.jpg new file mode 100644 index 0000000..c263f4c Binary files /dev/null and b/assets/problems/215.kth-largest-element-in-an-array-quick-select.jpg differ diff --git a/assets/problems/221.maximal-square-1.jpg b/assets/problems/221.maximal-square-1.jpg new file mode 100644 index 0000000..5e863c8 Binary files /dev/null and b/assets/problems/221.maximal-square-1.jpg differ diff --git a/assets/problems/221.maximal-square-2.jpg b/assets/problems/221.maximal-square-2.jpg new file mode 100644 index 0000000..efce066 Binary files /dev/null and b/assets/problems/221.maximal-square-2.jpg differ diff --git a/assets/problems/221.maximal-square-3.jpg b/assets/problems/221.maximal-square-3.jpg new file mode 100644 index 0000000..1217637 Binary files /dev/null and b/assets/problems/221.maximal-square-3.jpg differ diff --git a/assets/problems/229.majority-element-ii-1.jpeg b/assets/problems/229.majority-element-ii-1.jpeg new file mode 100644 index 0000000..1332140 Binary files /dev/null and b/assets/problems/229.majority-element-ii-1.jpeg differ diff --git a/assets/problems/229.majority-element-ii-2.jpeg b/assets/problems/229.majority-element-ii-2.jpeg new file mode 100644 index 0000000..9b5db2c Binary files /dev/null and b/assets/problems/229.majority-element-ii-2.jpeg differ diff --git a/assets/problems/23.merge-k-sorted-lists.gif b/assets/problems/23.merge-k-sorted-lists.gif new file mode 100644 index 0000000..c78cf94 Binary files /dev/null and b/assets/problems/23.merge-k-sorted-lists.gif differ diff --git a/assets/problems/232.implement-queue-using-stacks-1.jpg b/assets/problems/232.implement-queue-using-stacks-1.jpg new file mode 100644 index 0000000..b83c0b9 Binary files /dev/null and b/assets/problems/232.implement-queue-using-stacks-1.jpg differ diff --git a/assets/problems/232.implement-queue-using-stacks-2.jpg b/assets/problems/232.implement-queue-using-stacks-2.jpg new file mode 100644 index 0000000..93b2874 Binary files /dev/null and b/assets/problems/232.implement-queue-using-stacks-2.jpg differ diff --git a/assets/problems/232.implement-queue-using-stacks-3.jpg b/assets/problems/232.implement-queue-using-stacks-3.jpg new file mode 100644 index 0000000..252697b Binary files /dev/null and b/assets/problems/232.implement-queue-using-stacks-3.jpg differ diff --git a/assets/problems/232.implement-queue-using-stacks-4.jpg b/assets/problems/232.implement-queue-using-stacks-4.jpg new file mode 100644 index 0000000..80626fa Binary files /dev/null and b/assets/problems/232.implement-queue-using-stacks-4.jpg differ diff --git a/assets/problems/236.lowest-common-ancestor-of-a-binary-tree-1.png b/assets/problems/236.lowest-common-ancestor-of-a-binary-tree-1.png new file mode 100644 index 0000000..04be42e Binary files /dev/null and b/assets/problems/236.lowest-common-ancestor-of-a-binary-tree-1.png differ diff --git a/assets/problems/236.lowest-common-ancestor-of-a-binary-tree-2.png b/assets/problems/236.lowest-common-ancestor-of-a-binary-tree-2.png new file mode 100644 index 0000000..a604de3 Binary files /dev/null and b/assets/problems/236.lowest-common-ancestor-of-a-binary-tree-2.png differ diff --git a/assets/problems/238.product-of-array-except-self.png b/assets/problems/238.product-of-array-except-self.png new file mode 100644 index 0000000..fcd5227 Binary files /dev/null and b/assets/problems/238.product-of-array-except-self.png differ diff --git a/assets/problems/239.sliding-window-maximum.png b/assets/problems/239.sliding-window-maximum.png new file mode 100644 index 0000000..82d937d Binary files /dev/null and b/assets/problems/239.sliding-window-maximum.png differ diff --git a/assets/problems/240.search-a-2-d-matrix-ii.png b/assets/problems/240.search-a-2-d-matrix-ii.png new file mode 100644 index 0000000..61e4c62 Binary files /dev/null and b/assets/problems/240.search-a-2-d-matrix-ii.png differ diff --git a/assets/problems/25.reverse-nodes-in-k-groups-1.PNG b/assets/problems/25.reverse-nodes-in-k-groups-1.PNG new file mode 100644 index 0000000..904bccd Binary files /dev/null and b/assets/problems/25.reverse-nodes-in-k-groups-1.PNG differ diff --git a/assets/problems/25.reverse-nodes-in-k-groups-2.PNG b/assets/problems/25.reverse-nodes-in-k-groups-2.PNG new file mode 100644 index 0000000..d437b31 Binary files /dev/null and b/assets/problems/25.reverse-nodes-in-k-groups-2.PNG differ diff --git a/assets/problems/25.reverse-nodes-in-k-groups-3.png b/assets/problems/25.reverse-nodes-in-k-groups-3.png new file mode 100644 index 0000000..114561a Binary files /dev/null and b/assets/problems/25.reverse-nodes-in-k-groups-3.png differ diff --git a/assets/problems/263.ugly-number.png b/assets/problems/263.ugly-number.png new file mode 100644 index 0000000..89bf87c Binary files /dev/null and b/assets/problems/263.ugly-number.png differ diff --git a/assets/problems/29.divide-two-integers.png b/assets/problems/29.divide-two-integers.png new file mode 100644 index 0000000..81cfdec Binary files /dev/null and b/assets/problems/29.divide-two-integers.png differ diff --git a/assets/problems/295.find-median-from-data-stream-1.png b/assets/problems/295.find-median-from-data-stream-1.png new file mode 100644 index 0000000..cefe51e Binary files /dev/null and b/assets/problems/295.find-median-from-data-stream-1.png differ diff --git a/assets/problems/295.find-median-from-data-stream-2.png b/assets/problems/295.find-median-from-data-stream-2.png new file mode 100644 index 0000000..aab5f1c Binary files /dev/null and b/assets/problems/295.find-median-from-data-stream-2.png differ diff --git a/assets/problems/295.find-median-from-data-stream-3.png b/assets/problems/295.find-median-from-data-stream-3.png new file mode 100644 index 0000000..0a88e13 Binary files /dev/null and b/assets/problems/295.find-median-from-data-stream-3.png differ diff --git a/assets/problems/301.remove-invalid-parentheses.png b/assets/problems/301.remove-invalid-parentheses.png new file mode 100644 index 0000000..4cb061f Binary files /dev/null and b/assets/problems/301.remove-invalid-parentheses.png differ diff --git a/assets/problems/31.next-permutation-1.jpg b/assets/problems/31.next-permutation-1.jpg new file mode 100644 index 0000000..d26135d Binary files /dev/null and b/assets/problems/31.next-permutation-1.jpg differ diff --git a/assets/problems/31.next-permutation-2.jpg b/assets/problems/31.next-permutation-2.jpg new file mode 100644 index 0000000..ceede7d Binary files /dev/null and b/assets/problems/31.next-permutation-2.jpg differ diff --git a/assets/problems/31.next-permutation-3.jpg b/assets/problems/31.next-permutation-3.jpg new file mode 100644 index 0000000..9298285 Binary files /dev/null and b/assets/problems/31.next-permutation-3.jpg differ diff --git a/assets/problems/31.next-permutation-4.jpg b/assets/problems/31.next-permutation-4.jpg new file mode 100644 index 0000000..336a48f Binary files /dev/null and b/assets/problems/31.next-permutation-4.jpg differ diff --git a/assets/problems/32.longest-valid-parentheses.png b/assets/problems/32.longest-valid-parentheses.png new file mode 100644 index 0000000..0f655e5 Binary files /dev/null and b/assets/problems/32.longest-valid-parentheses.png differ diff --git a/assets/problems/334.increasing-triplet-subsequence.png b/assets/problems/334.increasing-triplet-subsequence.png new file mode 100644 index 0000000..553cbe0 Binary files /dev/null and b/assets/problems/334.increasing-triplet-subsequence.png differ diff --git a/assets/problems/342.power-of-four-1.png b/assets/problems/342.power-of-four-1.png new file mode 100644 index 0000000..10e7627 Binary files /dev/null and b/assets/problems/342.power-of-four-1.png differ diff --git a/assets/problems/342.power-of-four-2.png b/assets/problems/342.power-of-four-2.png new file mode 100644 index 0000000..6ecd473 Binary files /dev/null and b/assets/problems/342.power-of-four-2.png differ diff --git a/assets/problems/342.power-of-four.png b/assets/problems/342.power-of-four.png new file mode 100644 index 0000000..542bc71 Binary files /dev/null and b/assets/problems/342.power-of-four.png differ diff --git a/assets/problems/371.sum-of-two-integers-1.png b/assets/problems/371.sum-of-two-integers-1.png new file mode 100644 index 0000000..94c5da5 Binary files /dev/null and b/assets/problems/371.sum-of-two-integers-1.png differ diff --git a/assets/problems/371.sum-of-two-integers-2.png b/assets/problems/371.sum-of-two-integers-2.png new file mode 100644 index 0000000..c01a1b3 Binary files /dev/null and b/assets/problems/371.sum-of-two-integers-2.png differ diff --git a/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-1.jpg b/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-1.jpg new file mode 100644 index 0000000..e7c8f8b Binary files /dev/null and b/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-1.jpg differ diff --git a/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-2.jpg b/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-2.jpg new file mode 100644 index 0000000..7989f20 Binary files /dev/null and b/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-2.jpg differ diff --git a/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-3.jpg b/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-3.jpg new file mode 100644 index 0000000..4338701 Binary files /dev/null and b/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-3.jpg differ diff --git a/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-4.jpg b/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-4.jpg new file mode 100644 index 0000000..747d89a Binary files /dev/null and b/assets/problems/378.kth-smallest-element-in-a-sorted-matrix-4.jpg differ diff --git a/assets/problems/4.median-of-two-sorted-array-1.jpg b/assets/problems/4.median-of-two-sorted-array-1.jpg new file mode 100644 index 0000000..22a90d9 Binary files /dev/null and b/assets/problems/4.median-of-two-sorted-array-1.jpg differ diff --git a/assets/problems/4.median-of-two-sorted-array-2.jpg b/assets/problems/4.median-of-two-sorted-array-2.jpg new file mode 100644 index 0000000..6226133 Binary files /dev/null and b/assets/problems/4.median-of-two-sorted-array-2.jpg differ diff --git a/assets/problems/4.median-of-two-sorted-array-3.png b/assets/problems/4.median-of-two-sorted-array-3.png new file mode 100644 index 0000000..830af6d Binary files /dev/null and b/assets/problems/4.median-of-two-sorted-array-3.png differ diff --git a/assets/problems/4.median-of-two-sorted-array-4.png b/assets/problems/4.median-of-two-sorted-array-4.png new file mode 100644 index 0000000..7569300 Binary files /dev/null and b/assets/problems/4.median-of-two-sorted-array-4.png differ diff --git a/assets/problems/4.median-of-two-sorted-array-5.png b/assets/problems/4.median-of-two-sorted-array-5.png new file mode 100644 index 0000000..bde48c1 Binary files /dev/null and b/assets/problems/4.median-of-two-sorted-array-5.png differ diff --git a/assets/problems/416.partition-equal-subset-sum-1.png b/assets/problems/416.partition-equal-subset-sum-1.png new file mode 100644 index 0000000..2e2710f Binary files /dev/null and b/assets/problems/416.partition-equal-subset-sum-1.png differ diff --git a/assets/problems/416.partition-equal-subset-sum-2.png b/assets/problems/416.partition-equal-subset-sum-2.png new file mode 100644 index 0000000..0648a96 Binary files /dev/null and b/assets/problems/416.partition-equal-subset-sum-2.png differ diff --git a/assets/problems/42.trapping-rain-water-1.png b/assets/problems/42.trapping-rain-water-1.png new file mode 100644 index 0000000..578e81e Binary files /dev/null and b/assets/problems/42.trapping-rain-water-1.png differ diff --git a/assets/problems/437.path-sum-iii-1.jpg b/assets/problems/437.path-sum-iii-1.jpg new file mode 100644 index 0000000..f8112b7 Binary files /dev/null and b/assets/problems/437.path-sum-iii-1.jpg differ diff --git a/assets/problems/437.path-sum-iii-2.jpg b/assets/problems/437.path-sum-iii-2.jpg new file mode 100644 index 0000000..e19dd45 Binary files /dev/null and b/assets/problems/437.path-sum-iii-2.jpg differ diff --git a/assets/problems/454.4-sum-ii.png b/assets/problems/454.4-sum-ii.png new file mode 100644 index 0000000..7d283a5 Binary files /dev/null and b/assets/problems/454.4-sum-ii.png differ diff --git a/assets/problems/460.lfu-cache-1.jpg b/assets/problems/460.lfu-cache-1.jpg new file mode 100644 index 0000000..a948afa Binary files /dev/null and b/assets/problems/460.lfu-cache-1.jpg differ diff --git a/assets/problems/460.lfu-cache-2.jpg b/assets/problems/460.lfu-cache-2.jpg new file mode 100644 index 0000000..8a1eafe Binary files /dev/null and b/assets/problems/460.lfu-cache-2.jpg differ diff --git a/assets/problems/460.lfu-cache-3.jpg b/assets/problems/460.lfu-cache-3.jpg new file mode 100644 index 0000000..a711db1 Binary files /dev/null and b/assets/problems/460.lfu-cache-3.jpg differ diff --git a/assets/problems/460.lfu-cache-4.jpg b/assets/problems/460.lfu-cache-4.jpg new file mode 100644 index 0000000..e0c6da2 Binary files /dev/null and b/assets/problems/460.lfu-cache-4.jpg differ diff --git a/assets/problems/460.lfu-cache-5.jpg b/assets/problems/460.lfu-cache-5.jpg new file mode 100644 index 0000000..47b7640 Binary files /dev/null and b/assets/problems/460.lfu-cache-5.jpg differ diff --git a/assets/problems/460.lfu-cache-6.jpg b/assets/problems/460.lfu-cache-6.jpg new file mode 100644 index 0000000..b3a1d27 Binary files /dev/null and b/assets/problems/460.lfu-cache-6.jpg differ diff --git a/assets/problems/460.lfu-cache-7.jpg b/assets/problems/460.lfu-cache-7.jpg new file mode 100644 index 0000000..589783a Binary files /dev/null and b/assets/problems/460.lfu-cache-7.jpg differ diff --git a/assets/problems/460.lfu-cache-8.jpg b/assets/problems/460.lfu-cache-8.jpg new file mode 100644 index 0000000..e6880c3 Binary files /dev/null and b/assets/problems/460.lfu-cache-8.jpg differ diff --git a/assets/problems/474.ones-and-zeros-2d-dp.png b/assets/problems/474.ones-and-zeros-2d-dp.png new file mode 100644 index 0000000..71de6f5 Binary files /dev/null and b/assets/problems/474.ones-and-zeros-2d-dp.png differ diff --git a/assets/problems/48.rotate-image-1.png b/assets/problems/48.rotate-image-1.png new file mode 100644 index 0000000..ba71586 Binary files /dev/null and b/assets/problems/48.rotate-image-1.png differ diff --git a/assets/problems/48.rotate-image-2.png b/assets/problems/48.rotate-image-2.png new file mode 100644 index 0000000..64f6cbc Binary files /dev/null and b/assets/problems/48.rotate-image-2.png differ diff --git a/assets/problems/49.group-anagrams.png b/assets/problems/49.group-anagrams.png new file mode 100644 index 0000000..d9b7780 Binary files /dev/null and b/assets/problems/49.group-anagrams.png differ diff --git a/assets/problems/494.target-sum-2.png b/assets/problems/494.target-sum-2.png new file mode 100644 index 0000000..0a24946 Binary files /dev/null and b/assets/problems/494.target-sum-2.png differ diff --git a/assets/problems/494.target-sum-3.png b/assets/problems/494.target-sum-3.png new file mode 100644 index 0000000..f5d88c3 Binary files /dev/null and b/assets/problems/494.target-sum-3.png differ diff --git a/assets/problems/494.target-sum.png b/assets/problems/494.target-sum.png new file mode 100644 index 0000000..742716e Binary files /dev/null and b/assets/problems/494.target-sum.png differ diff --git a/assets/problems/5.longest-palindromic-substring-1.png b/assets/problems/5.longest-palindromic-substring-1.png new file mode 100644 index 0000000..525ab31 Binary files /dev/null and b/assets/problems/5.longest-palindromic-substring-1.png differ diff --git a/assets/problems/5.longest-palindromic-substring-2.png b/assets/problems/5.longest-palindromic-substring-2.png new file mode 100644 index 0000000..ad3ddc5 Binary files /dev/null and b/assets/problems/5.longest-palindromic-substring-2.png differ diff --git a/assets/problems/5.longest-palindromic-substring-3.png b/assets/problems/5.longest-palindromic-substring-3.png new file mode 100644 index 0000000..ab34c33 Binary files /dev/null and b/assets/problems/5.longest-palindromic-substring-3.png differ diff --git a/assets/problems/516.longest-palindromic-subsequence-1.png b/assets/problems/516.longest-palindromic-subsequence-1.png new file mode 100644 index 0000000..33e261f Binary files /dev/null and b/assets/problems/516.longest-palindromic-subsequence-1.png differ diff --git a/assets/problems/516.longest-palindromic-subsequence-2.png b/assets/problems/516.longest-palindromic-subsequence-2.png new file mode 100644 index 0000000..af2376f Binary files /dev/null and b/assets/problems/516.longest-palindromic-subsequence-2.png differ diff --git a/assets/problems/516.longest-palindromic-subsequence-3.png b/assets/problems/516.longest-palindromic-subsequence-3.png new file mode 100644 index 0000000..a5549de Binary files /dev/null and b/assets/problems/516.longest-palindromic-subsequence-3.png differ diff --git a/assets/problems/53.maximum-sum-subarray-divideconquer.png b/assets/problems/53.maximum-sum-subarray-divideconquer.png new file mode 100644 index 0000000..a5d7e61 Binary files /dev/null and b/assets/problems/53.maximum-sum-subarray-divideconquer.png differ diff --git a/assets/problems/53.maximum-sum-subarray-dp.png b/assets/problems/53.maximum-sum-subarray-dp.png new file mode 100644 index 0000000..ddb3d1c Binary files /dev/null and b/assets/problems/53.maximum-sum-subarray-dp.png differ diff --git a/assets/problems/54.spiral-matrix.jpg b/assets/problems/54.spiral-matrix.jpg new file mode 100644 index 0000000..09d79dc Binary files /dev/null and b/assets/problems/54.spiral-matrix.jpg differ diff --git a/assets/problems/547.friend-circle-1.png b/assets/problems/547.friend-circle-1.png new file mode 100644 index 0000000..ef6a32a Binary files /dev/null and b/assets/problems/547.friend-circle-1.png differ diff --git a/assets/problems/547.friend-circle-bfs.png b/assets/problems/547.friend-circle-bfs.png new file mode 100644 index 0000000..6c60eb3 Binary files /dev/null and b/assets/problems/547.friend-circle-bfs.png differ diff --git a/assets/problems/547.friend-circle-dfs.png b/assets/problems/547.friend-circle-dfs.png new file mode 100644 index 0000000..a137a29 Binary files /dev/null and b/assets/problems/547.friend-circle-dfs.png differ diff --git a/assets/problems/547.friend-circle-uf.png b/assets/problems/547.friend-circle-uf.png new file mode 100644 index 0000000..1e6ef6a Binary files /dev/null and b/assets/problems/547.friend-circle-uf.png differ diff --git a/assets/problems/560.subarray-sum-equals-k.jpg b/assets/problems/560.subarray-sum-equals-k.jpg new file mode 100644 index 0000000..0b8771c Binary files /dev/null and b/assets/problems/560.subarray-sum-equals-k.jpg differ diff --git a/assets/problems/575.distribute-candies.png b/assets/problems/575.distribute-candies.png new file mode 100644 index 0000000..46c6b62 Binary files /dev/null and b/assets/problems/575.distribute-candies.png differ diff --git a/assets/problems/62.unique-paths-1.png b/assets/problems/62.unique-paths-1.png new file mode 100644 index 0000000..ebcd2dc Binary files /dev/null and b/assets/problems/62.unique-paths-1.png differ diff --git a/assets/problems/62.unique-paths-2.png b/assets/problems/62.unique-paths-2.png new file mode 100644 index 0000000..39a64d9 Binary files /dev/null and b/assets/problems/62.unique-paths-2.png differ diff --git a/assets/problems/62.unique-paths-3.png b/assets/problems/62.unique-paths-3.png new file mode 100644 index 0000000..334241f Binary files /dev/null and b/assets/problems/62.unique-paths-3.png differ diff --git a/assets/problems/73.set-matrix-zeroes-1.png b/assets/problems/73.set-matrix-zeroes-1.png new file mode 100644 index 0000000..de396ab Binary files /dev/null and b/assets/problems/73.set-matrix-zeroes-1.png differ diff --git a/assets/problems/73.set-matrix-zeroes-2.png b/assets/problems/73.set-matrix-zeroes-2.png new file mode 100644 index 0000000..28062e4 Binary files /dev/null and b/assets/problems/73.set-matrix-zeroes-2.png differ diff --git a/assets/problems/79.word-search-1.png b/assets/problems/79.word-search-1.png new file mode 100644 index 0000000..5478c07 Binary files /dev/null and b/assets/problems/79.word-search-1.png differ diff --git a/assets/problems/79.word-search-2.png b/assets/problems/79.word-search-2.png new file mode 100644 index 0000000..1d888ab Binary files /dev/null and b/assets/problems/79.word-search-2.png differ diff --git a/assets/problems/79.word-search-3.png b/assets/problems/79.word-search-3.png new file mode 100644 index 0000000..4e206df Binary files /dev/null and b/assets/problems/79.word-search-3.png differ diff --git a/assets/problems/79.word-search-4.png b/assets/problems/79.word-search-4.png new file mode 100644 index 0000000..21970d5 Binary files /dev/null and b/assets/problems/79.word-search-4.png differ diff --git a/assets/problems/79.word-search-5.png b/assets/problems/79.word-search-5.png new file mode 100644 index 0000000..d4609cc Binary files /dev/null and b/assets/problems/79.word-search-5.png differ diff --git a/assets/problems/79.word-search-6.png b/assets/problems/79.word-search-6.png new file mode 100644 index 0000000..3258243 Binary files /dev/null and b/assets/problems/79.word-search-6.png differ diff --git a/assets/problems/79.word-search-7.png b/assets/problems/79.word-search-7.png new file mode 100644 index 0000000..7c13fb6 Binary files /dev/null and b/assets/problems/79.word-search-7.png differ diff --git a/assets/problems/79.word-search-en-1.png b/assets/problems/79.word-search-en-1.png new file mode 100644 index 0000000..9b9bf99 Binary files /dev/null and b/assets/problems/79.word-search-en-1.png differ diff --git a/assets/problems/88.merge-sorted-array-1.png b/assets/problems/88.merge-sorted-array-1.png new file mode 100644 index 0000000..e74c545 Binary files /dev/null and b/assets/problems/88.merge-sorted-array-1.png differ diff --git a/assets/problems/88.merge-sorted-array-2.png b/assets/problems/88.merge-sorted-array-2.png new file mode 100644 index 0000000..57b0811 Binary files /dev/null and b/assets/problems/88.merge-sorted-array-2.png differ diff --git a/assets/problems/88.merge-sorted-array-3.png b/assets/problems/88.merge-sorted-array-3.png new file mode 100644 index 0000000..bc2049a Binary files /dev/null and b/assets/problems/88.merge-sorted-array-3.png differ diff --git a/assets/problems/887.super-egg-drop-1.png b/assets/problems/887.super-egg-drop-1.png new file mode 100644 index 0000000..3450f47 Binary files /dev/null and b/assets/problems/887.super-egg-drop-1.png differ diff --git a/assets/problems/887.super-egg-drop-2.png b/assets/problems/887.super-egg-drop-2.png new file mode 100644 index 0000000..902a4e7 Binary files /dev/null and b/assets/problems/887.super-egg-drop-2.png differ diff --git a/assets/problems/912.sort-an-array-1.png b/assets/problems/912.sort-an-array-1.png new file mode 100644 index 0000000..3715baf Binary files /dev/null and b/assets/problems/912.sort-an-array-1.png differ diff --git a/assets/problems/912.sort-an-array-2.png b/assets/problems/912.sort-an-array-2.png new file mode 100644 index 0000000..9838c92 Binary files /dev/null and b/assets/problems/912.sort-an-array-2.png differ diff --git a/assets/problems/backtrack.png b/assets/problems/backtrack.png new file mode 100644 index 0000000..85f87f8 Binary files /dev/null and b/assets/problems/backtrack.png differ diff --git a/assets/problems/coin-change-2-opt.png b/assets/problems/coin-change-2-opt.png new file mode 100644 index 0000000..589a4bc Binary files /dev/null and b/assets/problems/coin-change-2-opt.png differ diff --git a/assets/problems/coin-change-2-wrong.png b/assets/problems/coin-change-2-wrong.png new file mode 100644 index 0000000..57164e6 Binary files /dev/null and b/assets/problems/coin-change-2-wrong.png differ diff --git a/assets/problems/coin-change-2.png b/assets/problems/coin-change-2.png new file mode 100644 index 0000000..b265ff8 Binary files /dev/null and b/assets/problems/coin-change-2.png differ diff --git a/assets/problems/koko-eating-bananas.png b/assets/problems/koko-eating-bananas.png new file mode 100644 index 0000000..c4af94e Binary files /dev/null and b/assets/problems/koko-eating-bananas.png differ diff --git a/assets/problems/search-in-rotated-sorted-array-1.jpg b/assets/problems/search-in-rotated-sorted-array-1.jpg new file mode 100644 index 0000000..b5c2901 Binary files /dev/null and b/assets/problems/search-in-rotated-sorted-array-1.jpg differ diff --git a/assets/problems/search-in-rotated-sorted-array-2.jpg b/assets/problems/search-in-rotated-sorted-array-2.jpg new file mode 100644 index 0000000..f0e2396 Binary files /dev/null and b/assets/problems/search-in-rotated-sorted-array-2.jpg differ diff --git a/assets/qq-group-chat.png b/assets/qq-group-chat.png new file mode 100644 index 0000000..f254a91 Binary files /dev/null and b/assets/qq-group-chat.png differ diff --git a/assets/thanks-gaving/9999.jpeg b/assets/thanks-gaving/9999.jpeg new file mode 100644 index 0000000..730ed6f Binary files /dev/null and b/assets/thanks-gaving/9999.jpeg differ diff --git a/assets/thanks-gaving/chongqing-1.jpeg b/assets/thanks-gaving/chongqing-1.jpeg new file mode 100644 index 0000000..56db420 Binary files /dev/null and b/assets/thanks-gaving/chongqing-1.jpeg differ diff --git a/assets/thanks-gaving/chongqing-2.jpeg b/assets/thanks-gaving/chongqing-2.jpeg new file mode 100644 index 0000000..fabaa30 Binary files /dev/null and b/assets/thanks-gaving/chongqing-2.jpeg differ diff --git a/assets/thanks-gaving/chongqing-3.jpeg b/assets/thanks-gaving/chongqing-3.jpeg new file mode 100644 index 0000000..88fb9e7 Binary files /dev/null and b/assets/thanks-gaving/chongqing-3.jpeg differ diff --git a/assets/thanks-gaving/daily-problems.jpg b/assets/thanks-gaving/daily-problems.jpg new file mode 100644 index 0000000..bec3df5 Binary files /dev/null and b/assets/thanks-gaving/daily-problems.jpg differ diff --git a/assets/thanks-gaving/first-commit.jpg b/assets/thanks-gaving/first-commit.jpg new file mode 100644 index 0000000..4ce5167 Binary files /dev/null and b/assets/thanks-gaving/first-commit.jpg differ diff --git a/assets/thanks-gaving/hello-github.jpeg b/assets/thanks-gaving/hello-github.jpeg new file mode 100644 index 0000000..58b5695 Binary files /dev/null and b/assets/thanks-gaving/hello-github.jpeg differ diff --git a/assets/thanks-gaving/ruanyifeng.jpeg b/assets/thanks-gaving/ruanyifeng.jpeg new file mode 100644 index 0000000..75715b7 Binary files /dev/null and b/assets/thanks-gaving/ruanyifeng.jpeg differ diff --git a/assets/thanks-gaving/star-history.jpg b/assets/thanks-gaving/star-history.jpg new file mode 100644 index 0000000..584ed4b Binary files /dev/null and b/assets/thanks-gaving/star-history.jpg differ diff --git a/assets/thanks-gaving/司徒正美.jpeg b/assets/thanks-gaving/司徒正美.jpeg new file mode 100644 index 0000000..e409f2e Binary files /dev/null and b/assets/thanks-gaving/司徒正美.jpeg differ diff --git a/assets/thanks-gaving/多语言支持.jpg b/assets/thanks-gaving/多语言支持.jpg new file mode 100644 index 0000000..875b2d3 Binary files /dev/null and b/assets/thanks-gaving/多语言支持.jpg differ diff --git a/assets/thanks-gaving/开发者头条.jpeg b/assets/thanks-gaving/开发者头条.jpeg new file mode 100644 index 0000000..857e062 Binary files /dev/null and b/assets/thanks-gaving/开发者头条.jpeg differ diff --git a/assets/thanks-gaving/日榜第一.jpeg b/assets/thanks-gaving/日榜第一.jpeg new file mode 100644 index 0000000..40a05a0 Binary files /dev/null and b/assets/thanks-gaving/日榜第一.jpeg differ diff --git a/assets/thanks-gaving/朋友圈宣传.jpeg b/assets/thanks-gaving/朋友圈宣传.jpeg new file mode 100644 index 0000000..3bf6993 Binary files /dev/null and b/assets/thanks-gaving/朋友圈宣传.jpeg differ diff --git a/assets/thanks-gaving/知乎点赞.jpeg b/assets/thanks-gaving/知乎点赞.jpeg new file mode 100644 index 0000000..4f6c51c Binary files /dev/null and b/assets/thanks-gaving/知乎点赞.jpeg differ diff --git a/assets/thanks-gaving/群聊-qq.jpg b/assets/thanks-gaving/群聊-qq.jpg new file mode 100644 index 0000000..73cc113 Binary files /dev/null and b/assets/thanks-gaving/群聊-qq.jpg differ diff --git a/assets/thanks-gaving/群聊-wechat.jpg b/assets/thanks-gaving/群聊-wechat.jpg new file mode 100644 index 0000000..26d3ae0 Binary files /dev/null and b/assets/thanks-gaving/群聊-wechat.jpg differ diff --git a/assets/thanks-gaving/英文主页.jpg b/assets/thanks-gaving/英文主页.jpg new file mode 100644 index 0000000..7848f1c Binary files /dev/null and b/assets/thanks-gaving/英文主页.jpg differ diff --git a/assets/thanks-gaving/英语进展.jpg b/assets/thanks-gaving/英语进展.jpg new file mode 100644 index 0000000..1a7dea7 Binary files /dev/null and b/assets/thanks-gaving/英语进展.jpg differ diff --git a/assets/thanks-gaving/量子论.jpeg b/assets/thanks-gaving/量子论.jpeg new file mode 100644 index 0000000..0d18466 Binary files /dev/null and b/assets/thanks-gaving/量子论.jpeg differ diff --git a/assets/thinkings/basic-data-structure-call-stack.png b/assets/thinkings/basic-data-structure-call-stack.png new file mode 100644 index 0000000..4f9af9e Binary files /dev/null and b/assets/thinkings/basic-data-structure-call-stack.png differ diff --git a/assets/thinkings/basic-data-structure-fiber-intro.png b/assets/thinkings/basic-data-structure-fiber-intro.png new file mode 100644 index 0000000..859a1f3 Binary files /dev/null and b/assets/thinkings/basic-data-structure-fiber-intro.png differ diff --git a/assets/thinkings/basic-data-structure-hooks.png b/assets/thinkings/basic-data-structure-hooks.png new file mode 100644 index 0000000..43ae176 Binary files /dev/null and b/assets/thinkings/basic-data-structure-hooks.png differ diff --git a/assets/thinkings/basic-data-structure-link-list.svg b/assets/thinkings/basic-data-structure-link-list.svg new file mode 100644 index 0000000..6f0971e --- /dev/null +++ b/assets/thinkings/basic-data-structure-link-list.svg @@ -0,0 +1,214 @@ + + + + + + + + + image/svg+xml + + + + + + + 12 + + + + + 99 + + + + + 37 + + + + + + + + + + + + + diff --git a/assets/thinkings/basic-data-structure-queue-1.png b/assets/thinkings/basic-data-structure-queue-1.png new file mode 100644 index 0000000..7279a69 Binary files /dev/null and b/assets/thinkings/basic-data-structure-queue-1.png differ diff --git a/assets/thinkings/basic-data-structure-queue-2.png b/assets/thinkings/basic-data-structure-queue-2.png new file mode 100644 index 0000000..620b900 Binary files /dev/null and b/assets/thinkings/basic-data-structure-queue-2.png differ diff --git a/assets/thinkings/basic-data-structure-queue.svg b/assets/thinkings/basic-data-structure-queue.svg new file mode 100644 index 0000000..4602fb4 --- /dev/null +++ b/assets/thinkings/basic-data-structure-queue.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + +Dequeue +Enqueue + + + + + + + + + + + + + + + +Back +Front + diff --git a/assets/thinkings/basic-data-structure-stack.png b/assets/thinkings/basic-data-structure-stack.png new file mode 100644 index 0000000..4fbe5e8 Binary files /dev/null and b/assets/thinkings/basic-data-structure-stack.png differ diff --git a/assets/thinkings/basic-tree.svg b/assets/thinkings/basic-tree.svg new file mode 100644 index 0000000..b56e325 --- /dev/null +++ b/assets/thinkings/basic-tree.svg @@ -0,0 +1,242 @@ + + + +2 + +7 + +5 + +2 + +6 + +9 + +5 + +11 + +4 + + \ No newline at end of file diff --git a/assets/thinkings/binary-tree-traversal-bfs.gif b/assets/thinkings/binary-tree-traversal-bfs.gif new file mode 100644 index 0000000..751a0c0 Binary files /dev/null and b/assets/thinkings/binary-tree-traversal-bfs.gif differ diff --git a/assets/thinkings/binary-tree-traversal-dfs.gif b/assets/thinkings/binary-tree-traversal-dfs.gif new file mode 100644 index 0000000..ca2b863 Binary files /dev/null and b/assets/thinkings/binary-tree-traversal-dfs.gif differ diff --git a/assets/thinkings/binary-tree-traversal-preorder.png b/assets/thinkings/binary-tree-traversal-preorder.png new file mode 100644 index 0000000..75f543f Binary files /dev/null and b/assets/thinkings/binary-tree-traversal-preorder.png differ diff --git a/assets/thinkings/bloom-filter-url.png b/assets/thinkings/bloom-filter-url.png new file mode 100644 index 0000000..aa12eaf Binary files /dev/null and b/assets/thinkings/bloom-filter-url.png differ diff --git a/assets/thinkings/bst.png b/assets/thinkings/bst.png new file mode 100644 index 0000000..a30f584 Binary files /dev/null and b/assets/thinkings/bst.png differ diff --git a/assets/thinkings/dynamic-programming-1.png b/assets/thinkings/dynamic-programming-1.png new file mode 100644 index 0000000..6735f17 Binary files /dev/null and b/assets/thinkings/dynamic-programming-1.png differ diff --git a/assets/thinkings/dynamic-programming-2.png b/assets/thinkings/dynamic-programming-2.png new file mode 100644 index 0000000..887eb3a Binary files /dev/null and b/assets/thinkings/dynamic-programming-2.png differ diff --git a/assets/thinkings/dynamic-programming-3.png b/assets/thinkings/dynamic-programming-3.png new file mode 100644 index 0000000..abebc7a Binary files /dev/null and b/assets/thinkings/dynamic-programming-3.png differ diff --git a/assets/thinkings/graph-1.png b/assets/thinkings/graph-1.png new file mode 100644 index 0000000..9158575 Binary files /dev/null and b/assets/thinkings/graph-1.png differ diff --git a/assets/thinkings/graph-2.png b/assets/thinkings/graph-2.png new file mode 100644 index 0000000..efd3c78 Binary files /dev/null and b/assets/thinkings/graph-2.png differ diff --git a/assets/thinkings/huffman-example-fix.png b/assets/thinkings/huffman-example-fix.png new file mode 100644 index 0000000..87549f8 Binary files /dev/null and b/assets/thinkings/huffman-example-fix.png differ diff --git a/assets/thinkings/huffman-example.png b/assets/thinkings/huffman-example.png new file mode 100644 index 0000000..a2c9028 Binary files /dev/null and b/assets/thinkings/huffman-example.png differ diff --git a/assets/thinkings/huffman-tree.webp b/assets/thinkings/huffman-tree.webp new file mode 100644 index 0000000..cb572a6 Binary files /dev/null and b/assets/thinkings/huffman-tree.webp differ diff --git a/assets/thinkings/max-heap.svg b/assets/thinkings/max-heap.svg new file mode 100644 index 0000000..e13c406 --- /dev/null +++ b/assets/thinkings/max-heap.svg @@ -0,0 +1,709 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + 100 + + + + + 19 + + + + + 36 + + + + + 17 + + + + + 3 + + + + + 25 + + + + + 1 + + + + + 2 + + + + + 7 + + + diff --git a/assets/thinkings/min-heap.png b/assets/thinkings/min-heap.png new file mode 100644 index 0000000..acdfe36 Binary files /dev/null and b/assets/thinkings/min-heap.png differ diff --git a/assets/wechat-group-chat.jpeg b/assets/wechat-group-chat.jpeg new file mode 100644 index 0000000..03fb3c4 Binary files /dev/null and b/assets/wechat-group-chat.jpeg differ diff --git a/assets/wechat-group-chat.jpg b/assets/wechat-group-chat.jpg new file mode 100644 index 0000000..5f39b87 Binary files /dev/null and b/assets/wechat-group-chat.jpg differ diff --git a/backlog/101.symmetric-tree.js b/backlog/101.symmetric-tree.js new file mode 100644 index 0000000..a2ebdcb --- /dev/null +++ b/backlog/101.symmetric-tree.js @@ -0,0 +1,41 @@ +/* + * @lc app=leetcode id=101 lang=javascript + * + * [101] Symmetric Tree + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function traversal(root) { + if (!root) return [null]; + + return [root.val].concat(traversal(root.left)).concat(traversal(root.right)); +} + +function reversedTraversal(root) { + if (!root) return [null]; + + return [root.val].concat(reversedTraversal(root.right)).concat(reversedTraversal(root.left)); +} +/** + * @param {TreeNode} root + * @return {boolean} + */ +var isSymmetric = function(root) { + if (root === null) return true; + + const left = traversal(root.left); + const right = reversedTraversal(root.right); + + // 判断left 和 right 是否一致 + if (left.length !== right.length) return false; + for(let i = 0; i < left.length; i++) { + if (left[i] !== right[i]) return false; + } + return true; +}; + diff --git a/backlog/108.convert-sorted-array-to-binary-search-tree.js b/backlog/108.convert-sorted-array-to-binary-search-tree.js new file mode 100644 index 0000000..0749fda --- /dev/null +++ b/backlog/108.convert-sorted-array-to-binary-search-tree.js @@ -0,0 +1,60 @@ +/* + * @lc app=leetcode id=108 lang=javascript + * + * [108] Convert Sorted Array to Binary Search Tree + * + * https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/ + * + * algorithms + * Easy (49.37%) + * Total Accepted: 255.2K + * Total Submissions: 507.2K + * Testcase Example: '[-10,-3,0,5,9]' + * + * Given an array where elements are sorted in ascending order, convert it to a + * height balanced BST. + * + * For this problem, a height-balanced binary tree is defined as a binary tree + * in which the depth of the two subtrees of every node never differ by more + * than 1. + * + * Example: + * + * + * Given the sorted array: [-10,-3,0,5,9], + * + * One possible answer is: [0,-3,9,-10,null,5], which represents the following + * height balanced BST: + * + * ⁠ 0 + * ⁠ / \ + * ⁠ -3 9 + * ⁠ / / + * ⁠-10 5 + * + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {number[]} nums + * @return {TreeNode} + */ +var sortedArrayToBST = function(nums) { + // 由于数组是排序好的,因此一个思路就是将数组分成两半,一半是左子树,另一半是右子树 + // 然后运用“树的递归性质”递归完成操作即可。 + if(nums.length === 0) return null; + const mid = nums.length >> 1; + const root = new TreeNode(nums[mid]); + + root.left = sortedArrayToBST(nums.slice(0, mid)); + root.right = sortedArrayToBST(nums.slice(mid + 1)) + return root; + // 扩展: 这道题启示我们如果是一个非排序的数组,我们可以先进行排序然后再按上述思路进行。 +}; + diff --git a/backlog/110.balanced-binary-tree.js b/backlog/110.balanced-binary-tree.js new file mode 100644 index 0000000..381d839 --- /dev/null +++ b/backlog/110.balanced-binary-tree.js @@ -0,0 +1,29 @@ +/* + * @lc app=leetcode id=110 lang=javascript + * + * [110] Balanced Binary Tree + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function maxDepth(root) { + if (root == null) return 0; + const l = maxDepth(root.left); + const r = maxDepth(root.right); + if (l === false || r === false) return false; + if (Math.abs(l - r) > 1) return false; + return 1 + Math.max(l, r); +} +/** + * @param {TreeNode} root + * @return {boolean} + */ +var isBalanced = function(root) { + if (root === null) return true; + return !!maxDepth(root) +}; + diff --git a/backlog/112.path-sum.js b/backlog/112.path-sum.js new file mode 100644 index 0000000..2b12870 --- /dev/null +++ b/backlog/112.path-sum.js @@ -0,0 +1,29 @@ +/* + * @lc app=leetcode id=112 lang=javascript + * + * [112] Path Sum + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @param {number} sum + * @return {boolean} + */ +var hasPathSum = function(root, sum) { + // check + if (root === null) return false; + // if it's leaf: + if (root.left === null && root.right === null) { + return root.val === sum; + } + return ( + hasPathSum(root.left, sum - root.val) || + hasPathSum(root.right, sum - root.val) + ); +}; diff --git a/backlog/137.single-number-ii.js b/backlog/137.single-number-ii.js new file mode 100644 index 0000000..5ca74d3 --- /dev/null +++ b/backlog/137.single-number-ii.js @@ -0,0 +1,23 @@ +/* + * @lc app=leetcode id=137 lang=javascript + * + * [137] Single Number II + */ +/** + * @param {number[]} nums + * @return {number} + */ +var singleNumber = function(nums) { + // [1,1,1,2] + let res = 0; + // 前提是nums中数字都不大于2^31 + for (let i = 0; i < 32; i++) { + let cnt = 0; + let bit = 1 << i; + for (let j = 0; j < nums.length; j++) { + if (nums[j] & bit) cnt++; + } + if (cnt % 3 != 0) res = res | bit; + } + return res; +}; diff --git a/backlog/141.linked-list-cycle.js b/backlog/141.linked-list-cycle.js new file mode 100644 index 0000000..34a775c --- /dev/null +++ b/backlog/141.linked-list-cycle.js @@ -0,0 +1,34 @@ +/* + * @lc app=leetcode id=141 lang=javascript + * + * [141] Linked List Cycle + */ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ + +/** + * @param {ListNode} head + * @return {boolean} + */ +var hasCycle = function(head) { + if (head === null) return false; + if (head.next === null) return false; + + let fast = head.next; + let slow = head; + + while(fast && fast.next) { + if (fast === slow) return true; + slow = slow.next; + const next = fast.next; + fast = next && next.next; + } + + return false; +}; + diff --git a/backlog/160.intersection-of-two-linked-lists.js b/backlog/160.intersection-of-two-linked-lists.js new file mode 100644 index 0000000..9458731 --- /dev/null +++ b/backlog/160.intersection-of-two-linked-lists.js @@ -0,0 +1,62 @@ +/* + * @lc app=leetcode id=160 lang=javascript + * + * [160] Intersection of Two Linked Lists + */ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ + +/** + * @param {ListNode} headA + * @param {ListNode} headB + * @return {ListNode} + */ +var getIntersectionNode = function(headA, headB) { + // 网上精妙的解法没看懂 + // see : https://leetcode.com/problems/intersection-of-two-linked-lists/discuss/49789/My-accepted-simple-and-shortest-C%2B%2B-code-with-comments-explaining-the-algorithm.-Any-comments-or-improvements + if (headA === null || headB === null) return null; + let lenA = 0; + let lenB = 0; + + let curA = headA; + let curB = headB; + + while(curA) { + curA = curA.next; + lenA += 1; + } + while(curB) { + curB = curB.next; + lenB += 1; + } + + const gap = Math.abs(lenA - lenB); + let cur = 0; + curA = headA; + curB = headB; + + if (lenA > lenB) { + while(cur < gap) { + cur++; + curA = curA.next; + } + } else { + while(cur < gap) { + cur++; + curB = curB.next; + } + } + + while(curA !== curB) { + curA = curA.next; + curB = curB.next; + } + + return curA; +}; + diff --git a/backlog/189.rotate-array.js b/backlog/189.rotate-array.js new file mode 100644 index 0000000..266ef13 --- /dev/null +++ b/backlog/189.rotate-array.js @@ -0,0 +1,64 @@ +/* + * @lc app=leetcode id=189 lang=javascript + * + * [189] Rotate Array + * + * https://leetcode.com/problems/rotate-array/description/ + * + * algorithms + * Easy (29.07%) + * Total Accepted: 287.3K + * Total Submissions: 966.9K + * Testcase Example: '[1,2,3,4,5,6,7]\n3' + * + * Given an array, rotate the array to the right by k steps, where k is + * non-negative. + * + * Example 1: + * + * + * Input: [1,2,3,4,5,6,7] and k = 3 + * Output: [5,6,7,1,2,3,4] + * Explanation: + * rotate 1 steps to the right: [7,1,2,3,4,5,6] + * rotate 2 steps to the right: [6,7,1,2,3,4,5] + * rotate 3 steps to the right: [5,6,7,1,2,3,4] + * + * + * Example 2: + * + * + * Input: [-1,-100,3,99] and k = 2 + * Output: [3,99,-1,-100] + * Explanation: + * rotate 1 steps to the right: [99,-1,-100,3] + * rotate 2 steps to the right: [3,99,-1,-100] + * + * + * Note: + * + * + * Try to come up as many solutions as you can, there are at least 3 different + * ways to solve this problem. + * Could you do it in-place with O(1) extra space? + * + */ +/** + * @param {number[]} nums + * @param {number} k + * @return {void} Do not return anything, modify nums in-place instead. + */ +var rotate = function(nums, k) { + // 就像扩容一样操作 + k = k % nums.length; + const n = nums.length; + + for (let i = nums.length - 1; i >= 0; i--) { + nums[i + k] = nums[i]; + } + + for (let i = 0; i < k; i++) { + nums[i] = nums[n + i]; + } + nums.length = n; +}; diff --git a/backlog/202.happy-number.js b/backlog/202.happy-number.js new file mode 100644 index 0000000..6fc898e --- /dev/null +++ b/backlog/202.happy-number.js @@ -0,0 +1,60 @@ +/* + * @lc app=leetcode id=202 lang=javascript + * + * [202] Happy Number + * + * https://leetcode.com/problems/happy-number/description/ + * + * algorithms + * Easy (44.36%) + * Total Accepted: 227.2K + * Total Submissions: 505.7K + * Testcase Example: '19' + * + * Write an algorithm to determine if a number is "happy". + * + * A happy number is a number defined by the following process: Starting with + * any positive integer, replace the number by the sum of the squares of its + * digits, and repeat the process until the number equals 1 (where it will + * stay), or it loops endlessly in a cycle which does not include 1. Those + * numbers for which this process ends in 1 are happy numbers. + * + * Example:  + * + * + * Input: 19 + * Output: true + * Explanation: + * 1^2 + 9^2 = 82 + * 8^2 + 2^2 = 68 + * 6^2 + 8^2 = 100 + * 1^2 + 0^2 + 0^2 = 1 + * + */ +function squareSum(n) { + let sum = 0, tmp; + while (n) { + tmp = n % 10; + sum += tmp * tmp; + n = Math.floor(n / 10); + } + return sum; +} + +function isHappyWithMapper(n, visited) { + if (n === 1) return true; + if (visited[n]) return false; + visited[n] = true; + + return isHappyWithMapper(squareSum(n), visited); +} +/** + * @param {number} n + * @return {boolean} + */ +var isHappy = function(n) { + const visited = {}; + + return isHappyWithMapper(n, visited); +}; + diff --git a/backlog/204.count-primes.js b/backlog/204.count-primes.js new file mode 100644 index 0000000..5c4682c --- /dev/null +++ b/backlog/204.count-primes.js @@ -0,0 +1,58 @@ +/* + * @lc app=leetcode id=204 lang=javascript + * + * [204] Count Primes + * + * https://leetcode.com/problems/count-primes/description/ + * + * algorithms + * Easy (28.33%) + * Total Accepted: 229.8K + * Total Submissions: 798.7K + * Testcase Example: '10' + * + * Count the number of prime numbers less than a non-negative number, n. + * + * Example: + * + * + * Input: 10 + * Output: 4 + * Explanation: There are 4 prime numbers less than 10, they are 2, 3, 5, 7. + * + * + */ +/** + * @param {number} n + * @return {number} + */ +var countPrimes = function(n) { + // tag: 数论 + // if (n <= 2) return 0; + // let compositionCount = 0; + // for(let i = 3; i < n; i++) { + // for(let j = i - 1; j > 1 ; j--) { + // if (i % j === 0) { + // compositionCount++; + // break; // 找到一个就可以证明它不是质数了 + // } + // } + // } + // return n - compositionCount - 2; // 需要减去1和n这两个数字 + + + // 上面的方法会超时,因此我们需要进行优化 + // 数学角度来看,如果一个数字可以分解为两个数字相乘(这两个数字不包括0和它本身),那么它就是合数 + const compositions = []; // compositions[i] 表示i是否是合数 + let count = 0; + for(let i = 2; i < n; i++) { + if (!compositions[i]) count++; + for(let j = 2; i * j < n; j++) { + compositions[i * j] = true; + } + } + + return count; + +}; + diff --git a/backlog/21.merge-two-sorted-lists.js b/backlog/21.merge-two-sorted-lists.js new file mode 100644 index 0000000..53ee929 --- /dev/null +++ b/backlog/21.merge-two-sorted-lists.js @@ -0,0 +1,71 @@ +/* + * @lc app=leetcode id=21 lang=javascript + * + * [21] Merge Two Sorted Lists + * + * https://leetcode.com/problems/merge-two-sorted-lists/description/ + * + * algorithms + * Easy (46.02%) + * Total Accepted: 562.7K + * Total Submissions: 1.2M + * Testcase Example: '[1,2,4]\n[1,3,4]' + * + * Merge two sorted linked lists and return it as a new list. The new list + * should be made by splicing together the nodes of the first two lists. + * + * Example: + * + * Input: 1->2->4, 1->3->4 + * Output: 1->1->2->3->4->4 + * + * + */ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} l1 + * @param {ListNode} l2 + * @return {ListNode} + */ +var mergeTwoLists = function(l1, l2) { + let current = new ListNode(); + const dummy = current; + + while (l1 || l2) { + if (!l1) { + current.next = l2; + return dummy.next; + } else if (!l2) { + current.next = l1; + return dummy.next; + } + + if (l1.val <= l2.val) { + current.next = l1; + l1 = l1.next; + } else { + current.next = l2; + l2 = l2.next; + } + + current = current.next; + } + + return dummy.next; + + // if (l1 === null) return l2; + // if (l2 === null) return l1; + // if (l1.val < l2.val) { + // l1.next = mergeTwoLists(l1.next, l2); + // return l1; + // } else { + // l2.next = mergeTwoLists(l1, l2.next); + // return l2; + // } +}; diff --git a/backlog/217.contains-duplicate.js b/backlog/217.contains-duplicate.js new file mode 100644 index 0000000..009758d --- /dev/null +++ b/backlog/217.contains-duplicate.js @@ -0,0 +1,55 @@ +/* + * @lc app=leetcode id=217 lang=javascript + * + * [217] Contains Duplicate + * + * https://leetcode.com/problems/contains-duplicate/description/ + * + * algorithms + * Easy (50.92%) + * Total Accepted: 324K + * Total Submissions: 628.5K + * Testcase Example: '[1,2,3,1]' + * + * Given an array of integers, find if the array contains any duplicates. + * + * Your function should return true if any value appears at least twice in the + * array, and it should return false if every element is distinct. + * + * Example 1: + * + * + * Input: [1,2,3,1] + * Output: true + * + * Example 2: + * + * + * Input: [1,2,3,4] + * Output: false + * + * Example 3: + * + * + * Input: [1,1,1,3,3,4,3,2,4,2] + * Output: true + * + */ +/** + * @param {number[]} nums + * @return {boolean} + */ +var containsDuplicate = function(nums) { + // 1. 暴力两层循环两两比较, 时间复杂度O(n^2) 空间复杂度O(1) + + // 2. 先排序,之后比较前后元素是否一致即可,一层循环即可,如果排序使用的比较排序的话时间复杂度O(nlogn) 空间复杂度O(1) + + // 3. 用hashmap ,时间复杂度O(n) 空间复杂度O(n) + const visited = {}; + for(let i = 0; i < nums.length; i++) { + if (visited[nums[i]]) return true; + visited[nums[i]] = true; + } + return false; +}; + diff --git a/backlog/268.missing-number.js b/backlog/268.missing-number.js new file mode 100644 index 0000000..cf7850b --- /dev/null +++ b/backlog/268.missing-number.js @@ -0,0 +1,50 @@ +/* + * @lc app=leetcode id=268 lang=javascript + * + * [268] Missing Number + * + * https://leetcode.com/problems/missing-number/description/ + * + * algorithms + * Easy (47.60%) + * Total Accepted: 267.7K + * Total Submissions: 556.2K + * Testcase Example: '[3,0,1]' + * + * Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, + * find the one that is missing from the array. + * + * Example 1: + * + * + * Input: [3,0,1] + * Output: 2 + * + * + * Example 2: + * + * + * Input: [9,6,4,2,3,5,7,0,1] + * Output: 8 + * + * + * Note: + * Your algorithm should run in linear runtime complexity. Could you implement + * it using only constant extra space complexity? + */ +/** + * @param {number[]} nums + * @return {number} + */ +var missingNumber = function(nums) { + // 缺失的数字一定是 0 到 n 之间的一个数字 + + // 这是一道数论的题目 + // 这里用到了一条性质: sum([1,n]) = n * (n+1) / 2 + let sum = 0; + for(let num of nums) + sum += num; + + return (nums.length * (nums.length + 1) )/ 2 - sum; +}; + diff --git a/backlog/278.first-bad-version.js b/backlog/278.first-bad-version.js new file mode 100644 index 0000000..9057f8f --- /dev/null +++ b/backlog/278.first-bad-version.js @@ -0,0 +1,39 @@ +/* + * @lc app=leetcode id=278 lang=javascript + * + * [278] First Bad Version + */ +/** + * Definition for isBadVersion() + * + * @param {integer} version number + * @return {boolean} whether the version is bad + * isBadVersion = function(version) { + * ... + * }; + */ + +/** + * @param {function} isBadVersion() + * @return {function} + */ +var solution = function(isBadVersion) { + /** + * @param {integer} n Total versions + * @return {integer} The first bad version + */ + return function(n) { + let start = 0; + let end = n; + while(start <= end) { + if (start === end) return start; + const mid = start + ((end - start) >> 1); + if (isBadVersion(mid)) { + end = mid; + } else { + start = mid + 1; + } + } + }; +}; + diff --git a/backlog/287.find-the-duplicate-number.js b/backlog/287.find-the-duplicate-number.js new file mode 100644 index 0000000..45f7d4d --- /dev/null +++ b/backlog/287.find-the-duplicate-number.js @@ -0,0 +1,18 @@ +/* + * @lc app=leetcode id=287 lang=javascript + * + * [287] Find the Duplicate Number + */ +/** + * @param {number[]} nums + * @return {number} + */ +var findDuplicate = function(nums) { + // Input: [1,3,4,2,2] + // Output: 2 + nums.sort(); + + for(let i = 0; i < nums.length; i++) { + if (nums[i] === nums[i + 1]) return nums[i]; + } +}; diff --git a/backlog/300.longest-increasing-subsequence.js b/backlog/300.longest-increasing-subsequence.js new file mode 100644 index 0000000..40ef828 --- /dev/null +++ b/backlog/300.longest-increasing-subsequence.js @@ -0,0 +1,45 @@ +/* + * @lc app=leetcode id=300 lang=javascript + * + * [300] Longest Increasing Subsequence + */ +/** + * @param {number[]} nums + * @return {number} + */ +var lengthOfLIS = function(nums) { + // 时间复杂度O(n^2) + // if (nums.length === 0) return 0; + // const dp = Array(nums.length).fill(1); + // let max = 1; + + // for (let i = 0; i < nums.length; i++) { + // for (let j = 0; j < i; j++) { + // if (nums[i] > nums[j]) { + // dp[i] = Math.max(dp[j] + 1, dp[i]); + // } + // max = Math.max(max, dp[i]); + // } + // } + + // return max; + + // [ 10, 9, 2, 5, 3, 7, 101, 18 ] + // [ 2, 3, 5, 7, 9, 10, 18, 101 ] + + // 参考: https://leetcode.com/problems/longest-increasing-subsequence/discuss/74824/JavaPython-Binary-search-O(nlogn)-time-with-explanation + // const tails = []; + // for (let i = 0; i < nums.length; i++) { + // let left = 0; + // let right = tails.length; + // while (left < right) { + // const mid = left + (right - left) / 2; // 防止溢出 + // if (tails[mid] < nums[i]) left = mid + 1; + // else right = mid; + // } + // // 说明nums[i] 比如tails中所有数字都大,我们直接push + // if (right === tails.length) tails.push(nums[i]); + // else tails[right] = nums[i]; // 否则我们修改tails[right] + // } + // return tails.length; +}; diff --git a/backlog/307.range-sum-query-mutable.js b/backlog/307.range-sum-query-mutable.js new file mode 100644 index 0000000..a14b720 --- /dev/null +++ b/backlog/307.range-sum-query-mutable.js @@ -0,0 +1,143 @@ +/* + * @lc app=leetcode id=307 lang=javascript + * + * [307] Range Sum Query - Mutable + */ +/** + * @param {number[]} nums + */ +// var NumArray = function(nums) { +// this.nums = nums; +// }; + +// /** +// * @param {number} i +// * @param {number} val +// * @return {void} +// */ +// NumArray.prototype.update = function(i, val) { +// this.nums[i] = val; +// }; + +// /** +// * @param {number} i +// * @param {number} j +// * @return {number} +// */ +// NumArray.prototype.sumRange = function(i, j) { +// let res = 0; +// for (let k = i; k < j + 1; k++) { +// res += this.nums[k]; +// } + +// return res; +// }; + +/** + * @param {number[]} nums + */ +var NumArray = function(nums) { + this.nums = nums; + + // Init array representation of segment tree. + this.segmentTree = []; + + const l = 0; + const r = this.nums.length - 1; + const cur = 0; + this.buildTreeRecursively(l, r, cur); +}; + +NumArray.prototype.buildTreeRecursively = function(l, r, cur) { + // If low input index and high input index are equal that would mean + // the we have finished splitting and we are already came to the leaf + // of the segment tree. We need to copy this leaf value from input + // array to segment tree. + if (l === r) { + return (this.segmentTree[cur] = this.nums[r]); + } + + // Split input array on two halves and process them recursively. + const m = Math.floor((l + r) / 2); + // Process left half of the input array. + this.buildTreeRecursively(l, m, this.getLeftChildIndex(cur)); + // Process right half of the input array. + this.buildTreeRecursively(m + 1, r, this.getRightChildIndex(cur)); + + // Once every tree leaf is not empty we're able to build tree bottom up using + // provided operation function. + this.segmentTree[cur] = this.operation( + this.segmentTree[this.getLeftChildIndex(cur)], + this.segmentTree[this.getRightChildIndex(cur)] + ); +}; + +/** + * @param {number} i + * @param {number} val + * @return {void} + */ +NumArray.prototype.update = function(i, val) { + this.nums[i] = val; +}; + +/** + * @param {number} i + * @param {number} j + * @return {number} + */ +NumArray.prototype.sumRange = function(i, j) { + const l = 0; + const r = this.nums.length - 1; + const cur = 0; + + return this.rangeQueryRecursive(i, j, l, r, cur); +}; + +NumArray.prototype.rangeQueryRecursive = function(i, j, l, r, cur) { + if (i <= l && j >= r) { + // Total overlap. + return this.segmentTree[cur]; + } + + if (i > r || j < l) { + // No overlap. + return this.operationFallback; + } + + // Partial overlap. + const m = Math.floor((l + r) / 2); + + const leftOperationResult = this.rangeQueryRecursive( + i, + j, + l, + m, + this.getLeftChildIndex(cur) + ); + + const rightOperationResult = this.rangeQueryRecursive( + i, + j, + m + 1, + r, + this.getRightChildIndex(cur) + ); + + return this.sumRange(leftOperationResult, rightOperationResult); +}; + +NumArray.prototype.getLeftChildIndex = function (parentIndex) { + return (2 * parentIndex) + 1; +} + +NumArray.prototype.getRightChildIndex = function (parentIndex) { + return (2 * parentIndex) + 2; +} + +/** + * Your NumArray object will be instantiated and called as such: + * var obj = new NumArray(nums) + * obj.update(i,val) + * var param_2 = obj.sumRange(i,j) + */ diff --git a/backlog/315.count-of-smaller-numbers-after-self.js b/backlog/315.count-of-smaller-numbers-after-self.js new file mode 100644 index 0000000..9b06b42 --- /dev/null +++ b/backlog/315.count-of-smaller-numbers-after-self.js @@ -0,0 +1,85 @@ +/* + * @lc app=leetcode id=315 lang=javascript + * + * [315] Count of Smaller Numbers After Self + */ +/** + * @param {number[]} nums + * @return {number[]} + */ +var countSmaller = function(nums) { + // Input: [5,2,6,1] + // Output: [2,1,1,0] + // 暴力法: + // const res = Array(nums.length).fill(0); + // for (let i = 0; i < nums.length - 1; i++) { + // for (let j = i; j < nums.length; j++) { + // if (nums[i] > nums[j]) { + // res[i] += 1; + // } + // } + // } + + // return res; + // 归并排序 + const res = Array(nums.length).fill(0); + + function merge(arr, l, m, r, res) { + let i, j, k; + const n1 = m - l + 1; + const n2 = r - m; + + /* create temp arrays */ + const L = Array(n1); + const R = Array(n2); + + /* Copy data to temp arrays L[] and R[] */ + for (i = 0; i < n1; i++) L[i] = arr[l + i]; + for (j = 0; j < n2; j++) R[j] = arr[m + 1 + j]; + + /* Merge the temp arrays back into arr[l..r]*/ + i = 0; // Initial index of first subarray + j = 0; // Initial index of second subarray + k = l; // Initial index of merged subarray + while (i < n1 && j < n2) { + if (L[i] <= R[j]) { + arr[k] = L[i]; + i++; + } else { + arr[k] = R[j]; + res[k] += 1; + j++; + } + k++; + } + + /* Copy the remaining elements of L[], if there + are any */ + while (i < n1) { + arr[k] = L[i]; + i++; + k++; + } + + /* Copy the remaining elements of R[], if there + are any */ + while (j < n2) { + arr[k] = R[j]; + j++; + k++; + } + } + function mergeSort(arr, l, r, res) { + if (l < r) { + const m = l + ((r - l) >> 1); + + mergeSort(arr, l, m, res); + mergeSort(arr, m + 1, r, res); + + merge(arr, l, m, r, res); + } + return res; + } + + return mergeSort(nums, 0, nums.length - 1, res); +}; diff --git a/backlog/326.power-of-three.js b/backlog/326.power-of-three.js new file mode 100644 index 0000000..8f06766 --- /dev/null +++ b/backlog/326.power-of-three.js @@ -0,0 +1,59 @@ +/* + * @lc app=leetcode id=326 lang=javascript + * + * [326] Power of Three + * + * https://leetcode.com/problems/power-of-three/description/ + * + * algorithms + * Easy (41.43%) + * Total Accepted: 178.8K + * Total Submissions: 430.4K + * Testcase Example: '27' + * + * Given an integer, write a function to determine if it is a power of three. + * + * Example 1: + * + * + * Input: 27 + * Output: true + * + * + * Example 2: + * + * + * Input: 0 + * Output: false + * + * Example 3: + * + * + * Input: 9 + * Output: true + * + * Example 4: + * + * + * Input: 45 + * Output: false + * + * Follow up: + * Could you do it without using any loop / recursion? + */ +/** + * @param {number} n + * @return {boolean} + */ +var isPowerOfThree = function(n) { + // tag: 数论 + // let i = 0; + // while(Math.pow(3, i) < n) { + // i++; + // } + // return Math.pow(3, i) === n; + + // 巧用整除 + return n > 0 && Math.pow(3, 19) % n === 0; +}; +// 扩展: 这个方法可以扩展到任意质数,合数则不行 diff --git a/backlog/337.house-robber-iii.js b/backlog/337.house-robber-iii.js new file mode 100644 index 0000000..27094d4 --- /dev/null +++ b/backlog/337.house-robber-iii.js @@ -0,0 +1,43 @@ +/* + * @lc app=leetcode id=337 lang=javascript + * + * [337] House Robber III + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function helper(root) { + if (root === null) return [0, 0]; + // 0: rob 1: notRob + const l = helper(root.left); + const r = helper(root.right); + + const robed = root.val + l[1] + r[1]; + const notRobed = Math.max(l[0], l[1]) + Math.max(r[0], r[1]); + + return [robed, notRobed]; +} +/** + * @param {TreeNode} root + * @return {number} + */ +var rob = function(root) { + // if (root === null) return 0; + // const notRobed = rob(root.left) + rob(root.right); + // const robed = + // root.val + + // rob(root.left && root.left.left) + + // rob(root.left && root.left.right) + + // rob(root.right && root.right.left) + + // rob(root.right && root.right.right); + + // return Math.max(notRobed, robed); + + // dp + const [robed, notRobed] = helper(root); + return Math.max(robed, notRobed); +}; diff --git a/backlog/338.counting-bits.js b/backlog/338.counting-bits.js new file mode 100644 index 0000000..92f4ff0 --- /dev/null +++ b/backlog/338.counting-bits.js @@ -0,0 +1,67 @@ +/* + * @lc app=leetcode id=338 lang=javascript + * + * [338] Counting Bits + * + * https://leetcode.com/problems/counting-bits/description/ + * + * algorithms + * Medium (64.04%) + * Total Accepted: 163.1K + * Total Submissions: 253K + * Testcase Example: '2' + * + * Given a non negative integer number num. For every numbers i in the range 0 + * ≤ i ≤ num calculate the number of 1's in their binary representation and + * return them as an array. + * + * Example 1: + * + * + * Input: 2 + * Output: [0,1,1] + * + * Example 2: + * + * + * Input: 5 + * Output: [0,1,1,2,1,2] + * + * + * Follow up: + * + * + * It is very easy to come up with a solution with run time + * O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a + * single pass? + * Space complexity should be O(n). + * Can you do it like a boss? Do it without using any builtin function like + * __builtin_popcount in c++ or in any other language. + * + */ +/** + * @param {number} num + * @return {number[]} + */ +var countBits = function(num) { + // tag: bit dp + // Time complexity: O(n) + // Space complexity: O(n) + const res = []; + res[0] = 0; + + // 10000100110101 + for (let i = 1; i <= num; i++) { + if ((i & 1) === 0) { + // 偶数 + // 偶数最后一位是0,因此右移一位对结果没有影响 + res[i] = res[i >> 1]; + } else { + // 奇数 + // 奇数最后一位是1,i - 1 的 位数 + 1 就是结果 + res[i] = res[i - 1] + 1; + } + } + + return res; +}; diff --git a/backlog/34.find-first-and-last-position-of-element-in-sorted-array.js b/backlog/34.find-first-and-last-position-of-element-in-sorted-array.js new file mode 100644 index 0000000..dc09f17 --- /dev/null +++ b/backlog/34.find-first-and-last-position-of-element-in-sorted-array.js @@ -0,0 +1,37 @@ +/* + * @lc app=leetcode id=34 lang=javascript + * + * [34] Find First and Last Position of Element in Sorted Array + */ +/** + * @param {number[]} nums + * @param {number} target + * @return {number[]} + */ +var searchRange = function(nums, target) { + // 题目要求时间复杂度为O(logn)因此很自然想到二分法 + let start = 0; + let end = nums.length - 1; + while (start <= end) { + const mid = start + ((end - start) >> 1); + + if (nums[mid] === target) { + let left = 0; + let right = 0; + + while (nums[mid - left] === target) { + left++; + } + while (nums[mid + right] === target) { + right++; + } + return [mid - left + 1, mid + right - 1]; + } else if (nums[mid] > target) { + end = mid - 1; + } else { + start = mid + 1; + } + } + + return [-1, -1]; +}; diff --git a/backlog/344.reverse-string.js b/backlog/344.reverse-string.js new file mode 100644 index 0000000..6ff57b2 --- /dev/null +++ b/backlog/344.reverse-string.js @@ -0,0 +1,55 @@ +/* + * @lc app=leetcode id=344 lang=javascript + * + * [344] Reverse String + * + * https://leetcode.com/problems/reverse-string/description/ + * + * algorithms + * Easy (62.81%) + * Total Accepted: 409.9K + * Total Submissions: 649.5K + * Testcase Example: '["h","e","l","l","o"]' + * + * Write a function that reverses a string. The input string is given as an + * array of characters char[]. + * + * Do not allocate extra space for another array, you must do this by modifying + * the input array in-place with O(1) extra memory. + * + * You may assume all the characters consist of printable ascii + * characters. + * + * + * + * + * Example 1: + * + * + * Input: ["h","e","l","l","o"] + * Output: ["o","l","l","e","h"] + * + * + * + * Example 2: + * + * + * Input: ["H","a","n","n","a","h"] + * Output: ["h","a","n","n","a","H"] + * + * + * + * + */ +/** + * @param {character[]} s + * @return {void} Do not return anything, modify s in-place instead. + */ +var reverseString = function(s) { + for(let i = 0; i < s.length >> 1; i++) { + const temp = s[i]; + s[i] = s[s.length - i - 1]; + s[s.length - i - 1] = temp; + } +}; + diff --git a/backlog/345.reverse-vowels-of-a-string.js b/backlog/345.reverse-vowels-of-a-string.js new file mode 100644 index 0000000..bcc5d14 --- /dev/null +++ b/backlog/345.reverse-vowels-of-a-string.js @@ -0,0 +1,37 @@ +/* + * @lc app=leetcode id=345 lang=javascript + * + * [345] Reverse Vowels of a String + */ +/** + * @param {string} s + * @return {string} + */ +var reverseVowels = function(s) { + const vowels = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U']; + const res = s.split(''); + + let start = 0; + let end = s.length - 1; + while(start < end) { + const startVowel = vowels.includes(s[start]); + const endVowel = vowels.includes(s[end]); + if (startVowel && endVowel) { + const temp = res[start]; + res[start] = res[end]; + res[end] = temp; + start++; + end--; + } else if (startVowel) { + end--; + } else if (endVowel) { + start++ + } else { + start++; + end--; + } + } + + return res.join(''); +}; + diff --git a/backlog/347.top-k-frequent-elements.js b/backlog/347.top-k-frequent-elements.js new file mode 100644 index 0000000..87e50cd --- /dev/null +++ b/backlog/347.top-k-frequent-elements.js @@ -0,0 +1,35 @@ +/* + * @lc app=leetcode id=347 lang=javascript + * + * [347] Top K Frequent Elements + */ +/** + * @param {number[]} nums + * @param {number} k + * @return {number[]} + */ +var topKFrequent = function(nums, k) { + // 这个算法的瓶颈在于排序算法,时间复杂度基本上是O(nlogn) 空间复杂度是O(n) + + const hashtable = {}; + const n = nums.length; + const res = []; + + for (let i = 0; i < n; i++) { + const num = nums[i]; + if (hashtable[num] !== void 0) { + hashtable[num + ""] += 1; + } else { + hashtable[num + ""] = 1; + } + } + // sort desc + const list = Object.entries(hashtable); + list.sort(([, countA], [, countB]) => countB - countA); + + for (let i = 0; i < k; i++) { + res.push(list[i][0]); + } + + return res; +}; diff --git a/backlog/350.intersection-of-two-arrays-ii.js b/backlog/350.intersection-of-two-arrays-ii.js new file mode 100644 index 0000000..4283af9 --- /dev/null +++ b/backlog/350.intersection-of-two-arrays-ii.js @@ -0,0 +1,67 @@ +/* + * @lc app=leetcode id=350 lang=javascript + * + * [350] Intersection of Two Arrays II + * + * https://leetcode.com/problems/intersection-of-two-arrays-ii/description/ + * + * algorithms + * Easy (46.84%) + * Total Accepted: 185.1K + * Total Submissions: 393.7K + * Testcase Example: '[1,2,2,1]\n[2,2]' + * + * Given two arrays, write a function to compute their intersection. + * + * Example 1: + * + * + * Input: nums1 = [1,2,2,1], nums2 = [2,2] + * Output: [2,2] + * + * + * + * Example 2: + * + * + * Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4] + * Output: [4,9] + * + * + * Note: + * + * + * Each element in the result should appear as many times as it shows in both + * arrays. + * The result can be in any order. + * + * + * Follow up: + * + * + * What if the given array is already sorted? How would you optimize your + * algorithm? + * What if nums1's size is small compared to nums2's size? Which algorithm is + * better? + * What if elements of nums2 are stored on disk, and the memory is limited such + * that you cannot load all elements into the memory at once? + * + * + */ +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number[]} + */ +var intersect = function(nums1, nums2) { + const res = []; + + for (let i = 0; i < nums1.length; i++) { + if (nums2.includes(nums1[i])) { // 这里我们对两个数组排序,然后二分查找, 时间复杂度nlogn + nums2[nums2.indexOf(nums1[i])] = null; + res.push(nums1[i]); + } + } + + return res; +}; diff --git a/backlog/387.first-unique-character-in-a-string.js b/backlog/387.first-unique-character-in-a-string.js new file mode 100644 index 0000000..fbda62c --- /dev/null +++ b/backlog/387.first-unique-character-in-a-string.js @@ -0,0 +1,43 @@ +/* + * @lc app=leetcode id=387 lang=javascript + * + * [387] First Unique Character in a String + * + * https://leetcode.com/problems/first-unique-character-in-a-string/description/ + * + * algorithms + * Easy (49.29%) + * Total Accepted: 255.6K + * Total Submissions: 513.8K + * Testcase Example: '"leetcode"' + * + * + * Given a string, find the first non-repeating character in it and return it's + * index. If it doesn't exist, return -1. + * + * Examples: + * + * s = "leetcode" + * return 0. + * + * s = "loveleetcode", + * return 2. + * + * + * + * + * Note: You may assume the string contain only lowercase letters. + * + */ +/** + * @param {string} s + * @return {number} + */ +var firstUniqChar = function(s) { + for (let i = 0; i < s.length; i++) { + if (s.indexOf(s[i]) === s.lastIndexOf(s[i])) { + return i; + } + } + return -1; +}; diff --git a/backlog/409.longest-palindrome.js b/backlog/409.longest-palindrome.js new file mode 100644 index 0000000..4a23753 --- /dev/null +++ b/backlog/409.longest-palindrome.js @@ -0,0 +1,29 @@ +/* + * @lc app=leetcode id=409 lang=javascript + * + * [409] Longest Palindrome + */ +/** + * @param {string} s + * @return {number} + */ +var longestPalindrome = function(s) { + // abccccdd + let res = 0; + let hasOdd = false; + const counts = Array("z".charCodeAt(0) + 1).fill(0); + for (let i = 0; i < s.length; i++) { + counts[s.charCodeAt(i)] += 1; + } + + for (let i = 0; i < counts.length; i++) { + if (counts[i] % 2 === 0) { + res += counts[i]; + } else { + hasOdd = true; + res += counts[i] - 1; + } + } + + return hasOdd ? res + 1 : res; +}; diff --git a/backlog/538.convert-bst-to-greater-tree.js b/backlog/538.convert-bst-to-greater-tree.js new file mode 100644 index 0000000..47f80e0 --- /dev/null +++ b/backlog/538.convert-bst-to-greater-tree.js @@ -0,0 +1,61 @@ +/* + * @lc app=leetcode id=538 lang=javascript + * + * [538] Convert BST to Greater Tree + * + * https://leetcode.com/problems/convert-bst-to-greater-tree/description/ + * + * algorithms + * Easy (50.04%) + * Total Accepted: 75.4K + * Total Submissions: 149K + * Testcase Example: '[5,2,13]' + * + * Given a Binary Search Tree (BST), convert it to a Greater Tree such that + * every key of the original BST is changed to the original key plus sum of all + * keys greater than the original key in BST. + * + * + * Example: + * + * Input: The root of a Binary Search Tree like this: + * ⁠ 5 + * ⁠ / \ + * ⁠ 2 13 + * + * Output: The root of a Greater Tree like this: + * ⁠ 18 + * ⁠ / \ + * ⁠ 20 13 + * + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {TreeNode} + */ +var convertBST = function(root) { + let res = 0; + function r(root) { + if (root === null) return null; + + r(root.right); + + root.val += res; + + res = +root.val; + + r(root.left); + + return root; + } + r(root); + return root; +}; diff --git a/backlog/540.single-element-in-a-sorted-array.js b/backlog/540.single-element-in-a-sorted-array.js new file mode 100644 index 0000000..b74545a --- /dev/null +++ b/backlog/540.single-element-in-a-sorted-array.js @@ -0,0 +1,42 @@ +/* + * @lc app=leetcode id=540 lang=javascript + * + * [540] Single Element in a Sorted Array + */ +/** + * @param {number[]} nums + * @return {number} + */ +var singleNonDuplicate = function(nums) { + // if (nums.length === 1) return nums[0] + // nums.sort(); + // for (let i = 0; i < nums.length; i++) { + // if (nums[i] === nums[i + 1]) { + // i++; + // } else { + // return nums[i]; + // } + // } + + let start = 0; + let end = nums.length - 1; + + while (start <= end) { + const mid = start + ((end - start) >> 1); + if (nums[mid] === nums[mid + 1]) { + if (mid % 2 === 0) { + start = mid + 1; + } else { + end = mid - 1; + } + } else if (nums[mid] === nums[mid - 1]) { + if (mid % 2 === 0) { + end = mid - 1; + } else { + start = mid + 1; + } + } else { + return nums[mid]; + } + } +}; diff --git a/backlog/543.diameter-of-binary-tree.js b/backlog/543.diameter-of-binary-tree.js new file mode 100644 index 0000000..8f4dd11 --- /dev/null +++ b/backlog/543.diameter-of-binary-tree.js @@ -0,0 +1,36 @@ +/* + * @lc app=leetcode id=543 lang=javascript + * + * [543] Diameter of Binary Tree + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function maxDepth(root, res) { + if (root === null) return 0; + + const l = maxDepth(root.left, res); + const r = maxDepth(root.right, res); + res.val = Math.max(res.val, l + r); + return 1 + Math.max(l, r); +} +/** + * @param {TreeNode} root + * @return {number} + */ +var diameterOfBinaryTree = function(root) { + // 如果不计算max, 直接1+ Math.max(maxLeft, maxRight), 得到的结果实际上是经过root节点的最大值,并不一定是总体最大值 + // 题目也做了说明, ”最大值不一定经过root“ + if (root === null) return 0; + const res = { + val: 0 + }; + + maxDepth(root, res); + + return res.val; +}; diff --git a/backlog/633.sum-of-square-numbers.js b/backlog/633.sum-of-square-numbers.js new file mode 100644 index 0000000..633fc6e --- /dev/null +++ b/backlog/633.sum-of-square-numbers.js @@ -0,0 +1,26 @@ +/* + * @lc app=leetcode id=633 lang=javascript + * + * [633] Sum of Square Numbers + */ +/** + * @param {number} c + * @return {boolean} + */ +var judgeSquareSum = function(c) { + let start = 0; + let end = Math.floor(Math.sqrt(c)); + + while(start <= end) { + const res = Math.pow(start, 2) + Math.pow(end, 2); + if (res < c) { + start++; + } else if (res > c){ + end--; + } else { + return true; + } + } + return false; +}; + diff --git a/backlog/680.valid-palindrome-ii.js b/backlog/680.valid-palindrome-ii.js new file mode 100644 index 0000000..ee426d6 --- /dev/null +++ b/backlog/680.valid-palindrome-ii.js @@ -0,0 +1,42 @@ +/* + * @lc app=leetcode id=680 lang=javascript + * + * [680] Valid Palindrome II + */ +function isPalindrome(s, pos) { + // deeee + let start = 0; + let end = s.length - 1; + + while(start <= end) { + if (start === pos) { + start++; + continue; + } else if (end === pos) { + end--; + continue; + } + if (s[start] !== s[end]) return false + start++; + end--; + } + return true; +} +/** + * @param {string} s + * @return {boolean} + */ +var validPalindrome = function(s) { + // 时间复杂度O(n^2) + let start = 0; + let end = s.length - 1; + for(let i = 0; i < s.length; i++) { + if (s[start] !== s[end]) { + return isPalindrome(s, start) || isPalindrome(s, end); + } + start++; + end--; + } + return true; +}; + diff --git a/backlog/93.restore-ip-addresses.js b/backlog/93.restore-ip-addresses.js new file mode 100644 index 0000000..558c48c --- /dev/null +++ b/backlog/93.restore-ip-addresses.js @@ -0,0 +1,34 @@ +/* + * @lc app=leetcode id=93 lang=javascript + * + * [93] Restore IP Addresses + */ +function backtrack(list, tempList, s, start) { + if (tempList.length === 4 && tempList.join("") === s && !list.includes(tempList.join("."))) { + list.push(tempList.join(".")); + } + if (tempList.length > 4) return; + + for (let i = start; i < s.length; i++) { + for (let j = 0; j < 3; j++) { + const r = s.slice(i, i + j + 1); + if (+r > 255) continue; + if (r[0] === '0' && r.length > 1) continue; + tempList.push(r); + backtrack(list, tempList, s, i + j + 1); + tempList.pop(); + } + } +} +/** + * @param {string} s + * @return {string[]} + */ +var restoreIpAddresses = function(s) { + // Given "25525511135", + // return ["255.255.11.135", "255.255.111.35"]. + if (s.length > 3 * 4) return []; + const list = []; + backtrack(list, [], s, 0); + return list; +}; diff --git a/backlog/958.check-completeness-of-a-binary-tree.js b/backlog/958.check-completeness-of-a-binary-tree.js new file mode 100644 index 0000000..fb9280c --- /dev/null +++ b/backlog/958.check-completeness-of-a-binary-tree.js @@ -0,0 +1,30 @@ +/* + * @lc app=leetcode id=958 lang=javascript + * + * [958] Check Completeness of a Binary Tree + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {boolean} + */ +var isCompleteTree = function(root) { + if (root === null) return root; + let cur = root; + const queue = []; + + while(cur !== null) { + queue.push(cur.left); + queue.push(cur.right); + cur = queue.shift(); + } + + return queue.filter(Boolean).length === 0; +}; + diff --git a/daily/2019-06-03.md b/daily/2019-06-03.md new file mode 100644 index 0000000..b52a540 --- /dev/null +++ b/daily/2019-06-03.md @@ -0,0 +1,209 @@ +## 每日一题 - Longest Common Prefix + +### 信息卡片 + +- 时间:2019-06-03 +- 题目链接:https://leetcode.com/problems/longest-common-prefix/ +- tag:`trie` `binary search` + +### 题目描述 + +``` +Write a function to find the longest common prefix string amongst an array of strings. + +If there is no common prefix, return an empty string "". + +Example 1: +Input: ["flower","flow","flight"] +Output: "fl" + +Example 2: +Input: ["dog","racecar","car"] +Output: "" +Explanation: There is no common prefix among the input strings. + +Note: +All given inputs are in lowercase letters a-z. +``` + +### 参考答案 + +#### 二分法 +- 找到字符串数组中长度最短字符串 +- longest common prefix 长度范围 0 ~ minLength +- 运用`binary search` + +参考代码 +```javascript +/* + * @lc app=leetcode id=14 lang=javascript + * + * [14] Longest Common Prefix + */ + +function isCommonPrefix(strs, middle) { + const prefix = strs[0].substring(0, middle); + for (let i = 1; i < strs.length; i++) { + if (!strs[i].startsWith(prefix)) return false; + } + + return true; +} +/** + * @param {string[]} strs + * @return {string} + */ +var longestCommonPrefix = function(strs) { + // trie 解法 + // 时间复杂度O(m) 空间复杂度O(m * n) + + // tag: 二分法 + // 时间复杂度 O(n*logm) 空间复杂度O(1) + if (strs.length === 0) return ""; + if (strs.length === 1) return strs[0]; + + let minLen = Number.MAX_VALUE; + + for (let i = 0; i < strs.length; i++) { + minLen = Math.min(minLen, strs[i].length); + } + + let low = 0; + let high = minLen; + + while (low <= high) { + const middle = (low + high) >> 1; + if (isCommonPrefix(strs, middle)) low = middle + 1; + else high = middle - 1; + } + + return strs[0].substring(0, (low + high) >> 1); +}; +``` +#### trie树 +以LeetCode另一道题[Implement Trie](https://leetcode.com/problems/implement-trie-prefix-tree/description/)的解法作为本题的参考思路, 具体代码可以自行补充完善 + +- 建立 Trie +- 遍历到有一个children有超过一个子元素为止 + + +Trie实现参考代码 +```javascript +/* + * @lc app=leetcode id=208 lang=javascript + * + * [208] Implement Trie (Prefix Tree) + * + * https://leetcode.com/problems/implement-trie-prefix-tree/description/ + * + * algorithms + * Medium (36.93%) + * Total Accepted: 172K + * Total Submissions: 455.5K + * Testcase Example: '["Trie","insert","search","search","startsWith","insert","search"]\n[[],["apple"],["apple"],["app"],["app"],["app"],["app"]]' + * + * Implement a trie with insert, search, and startsWith methods. + * + * Example: + * + * + * Trie trie = new Trie(); + * + * trie.insert("apple"); + * trie.search("apple"); // returns true + * trie.search("app"); // returns false + * trie.startsWith("app"); // returns true + * trie.insert("app"); + * trie.search("app"); // returns true + * + * + * Note: + * + * + * You may assume that all inputs are consist of lowercase letters a-z. + * All inputs are guaranteed to be non-empty strings. + * + * + */ +function TrieNode(val) { + this.val = val; + this.children = []; + this.isWord = false; +} + +function computeIndex(c) { + return c.charCodeAt(0) - "a".charCodeAt(0); +} +/** + * Initialize your data structure here. + */ +var Trie = function() { + this.root = new TrieNode(null); +}; + +/** + * Inserts a word into the trie. + * @param {string} word + * @return {void} + */ +Trie.prototype.insert = function(word) { + let ws = this.root; + for (let i = 0; i < word.length; i++) { + const c = word[i]; + const current = computeIndex(c); + if (!ws.children[current]) { + ws.children[current] = new TrieNode(c); + } + ws = ws.children[current]; + } + ws.isWord = true; +}; + +/** + * Returns if the word is in the trie. + * @param {string} word + * @return {boolean} + */ +Trie.prototype.search = function(word) { + let ws = this.root; + for (let i = 0; i < word.length; i++) { + const c = word[i]; + const current = computeIndex(c); + if (!ws.children[current]) return false; + ws = ws.children[current]; + } + return ws.isWord; +}; + +/** + * Returns if there is any word in the trie that starts with the given prefix. + * @param {string} prefix + * @return {boolean} + */ +Trie.prototype.startsWith = function(prefix) { + let ws = this.root; + for (let i = 0; i < prefix.length; i++) { + const c = prefix[i]; + const current = computeIndex(c); + if (!ws.children[current]) return false; + ws = ws.children[current]; + } + return true; +}; + +/** + * Your Trie object will be instantiated and called as such: + * var obj = new Trie() + * obj.insert(word) + * var param_2 = obj.search(word) + * var param_3 = obj.startsWith(prefix) + */ +``` +#### 暴力法 + +比较常规的一种解法, 大部分人采用的做法, 这里就不再赘述 + +### 其他优秀解答 +``` +暂无 +``` diff --git a/daily/2019-06-04.md b/daily/2019-06-04.md new file mode 100644 index 0000000..875093e --- /dev/null +++ b/daily/2019-06-04.md @@ -0,0 +1,76 @@ +# 毎日一题 - 134.Gas Station(加油站) + +## 信息卡片 + +* 时间:2019-06-04 +* 题目链接:https://leetcode-cn.com/problems/gas-station/ +* tag:Array +## 题目描述 +``` +There are N gas stations along a circular route, where the amount of gas at station i is gas[i]. + +You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations. + +Return the starting gas station's index if you can travel around the circuit once in the clockwise direction, otherwise return -1. +``` +## 参考答案 +1.暴力求解,时间复杂度O(n^2) +> +我们可以一次遍历gas,对于每一个gas我们依次遍历后面的gas,计算remian,如果remain一旦小于0,就说明不行,我们继续遍历下一个 +```js +// bad 时间复杂度0(n^2) +let remain = 0; +const n = gas.length; +for (let i = 0; i < gas.length;i++){ + remain += gas[i]; + remain -= cost[i]; + let count = 0; + while (remain >= 0){ + count++; + if (coun === n ) return i; + remain += gas[getIndex(i + count,n)]; + remain -= cost[getIndex(i + count,n)]; + } + remain = 0 +} +retirn -1; +``` + +2.比较巧妙的方法,时间复杂度是O(n) +> +这个方法基于两点: +> +2-1:如果站点i到达站点j走不通,那么从i到j之间的站点(比如k)出发一定都走不通。前提i(以及i到k之间)不会拖累总体(即remain >= 0)。 +> +2-2:如果cost总和大于gas总和,无论如何也无法走到终点,这个比较好理解。因此假如存在一个站点出发能够到达终点,其实就说明cost总和一定小于等于gas总和 +> +```js +const n = gas.length; +let total = 0; +let remain = 0; +let start = 0; + +for(let i = 0; i < n; i++){ + total += gas[i]; + total -= cost[i] + + remain += gas[i]; + remain -= cost[i]; + + // 如果remain < 0,说明从start到i走不通 + // 并且从start到i走不通,那么所有的solution中包含start到i的肯定都走不通 + // 因此我们重新从i + 1开始作为start + if (remain < 0){ + remain = 0; + start = i + 1; + } +} +// 事实上,我们遍历一遍,也就确定了每一个元素作为start是否可以走完一圈 + +// 如果costu总和大于gas总和,无论如何也无法走到终点 +return total >= 0? start : -1; +``` + +## 优秀解答 + +>暂缺 diff --git a/daily/2019-06-05.md b/daily/2019-06-05.md new file mode 100644 index 0000000..6f2896b --- /dev/null +++ b/daily/2019-06-05.md @@ -0,0 +1,108 @@ +## 每日一题 - Find All Numbers Disappeared in an Array + +### 信息卡片 + +- 时间:2019-06-05 +- 题目链接:https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/ +- tag:`array` + +### 题目描述 + +``` +Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once. + +Find all the elements of [1, n] inclusive that do not appear in this array. + +Could you do it without extra space and in O(n) runtime? You may assume the returned list does not count as extra space. + +Example: + +Input: +[4,3,2,7,8,2,3,1] + +Output: +[5,6] +``` + +### 参考答案 + +#### 使用额外的空间记录出现过的数字, 时间复杂度和空间复杂度皆为O(n) + +参考代码 +```javascript +/* + * @lc app=leetcode id=448 lang=javascript + * + * [448] Find All Numbers Disappeared in an Array + */ +/** + * @param {number[]} nums + * @return {number[]} + */ +var findDisappearedNumbers = function(nums) { + let allNums = []; + let res = []; + + for (let i = 0; i < nums.length; i++){ + allNums[nums[i] - 1] = true; + } + + for (let i = 0; i < nums.length; i++){ + if(!allNums[i]){ + res.push(i + 1); + } + } + return res; +}; +``` + +#### 充分利用题目 "You may assume the returned list does not count as extra space." + +- 用res记录哪些数字出现过 +- 最后遍历时, 判断res是否为空, 若是, 则证明未出现过, 将其写回res + +参考代码 +```javascript +/* + * @lc app=leetcode id=448 lang=javascript + * + * [448] Find All Numbers Disappeared in an Array + */ +/** + * @param {number[]} nums + * @return {number[]} + */ +var findDisappearedNumbers = function(nums) { + const res = []; + let cur = 0; + for(let i = 0; i < nums.length; i++) { + res[nums[i]] = true; + } + + for(let i = 0; i < nums.length; i++) { + if (res[i + 1] === void 0) { + res[cur++] = i + 1; + } + } + + res.length = cur; + + return res; +}; +``` + + +### 其他优秀解答 +利用python集合类型的特点: 元素唯一, 不存在相同元素 + +```python +class Solution(object): + def findDisappearedNumbers(self, nums): + """ + :type nums: List[int] + :rtype: List[int] + """ + ls = [i for i in range(1, len(nums)+1)] + + return list(set(ls) - set(nums)) +``` diff --git a/daily/2019-06-06.md b/daily/2019-06-06.md new file mode 100644 index 0000000..88f3cde --- /dev/null +++ b/daily/2019-06-06.md @@ -0,0 +1,100 @@ +# 毎日一题 - 739.Daily Temperatures + +## 信息卡片 + +- 时间:2019-06-06 +- 题目链接:https://leetcode.com/problems/daily-temperatures/ +- tag:`Array` `Stack` + +## 题目描述 + +``` +Given a list of daily temperatures T, return a list such that, for each day in the input, tells you how many days you would have to wait until a warmer temperature. If there is no future day for which this is possible, put 0 instead. + +For example, given the list of temperatures T = [73, 74, 75, 71, 69, 72, 76, 73], your output should be [1, 1, 4, 2, 1, 1, 0, 0]. + +Note: The length of temperatures will be in the range [1, 30000]. Each temperature will be an integer in the range [30, 100]. +``` + +## 参考答案 + +暴力,双层for循环。`效率很低` + +1. 外层是‘当天’T[i],内层是‘当天’之后T[j]; +2. 多少天之后比‘当天’温度高就是j-i; + +时间复杂度O(n^2), 空间复杂度O(1) + +参考JavaScript代码: + +```js +/** + * @param {number[]} T + * @return {number[]} + * 双层for循环 + */ +var dailyTemperatures = function(T) { + let result = []; + for(let i = 0; i < T.length; i++) { + result[i] = 0; + for(let j = i + 1; j < T.length; j++) { + if (T[i] < T[j]) { + result[i] = j - i; + break; + } + } + } + return result; +}; +``` + +使用栈,单调递减栈 + +1. for循环遍历数组,栈存T的下标i,返回结果数组result; +2. 拿栈顶元素peek与i比较,T[peek] >= T[i]则将i入栈,T[peek] < T[i]则栈顶值(原数组下标)位置的天数就是result[peek] = i - peek; +3. 栈顶元素出栈; +4. 重复2,3两步; + +时间复杂度O(n), 空间复杂度O(n) + +参考JavaScript代码: + +```js +/** + * @param {number[]} T + * @return {number[]} + * 递减栈; + */ +var dailyTemperatures = function(T) { + let stack = []; + let result = []; + for (let i = 0; i < T.length; i++) { + result[i] = 0; + while(stack.length > 0 && T[stack[stack.length - 1]] < T[i]) { + let peek = stack.pop(); + result[peek] = i - peek; + } + stack.push(i); + } + return result; +}; +``` + +Python3 代码: + +```python +class Solution: + def dailyTemperatures(self, T: List[int]) -> List[int]: + stack = [] + ans = [0] * len(T) + for i in range(len(T)): + while stack and T[i] > T[stack[-1]]: + peek = stack.pop(-1) + ans[peek] = i - peek + stack.append(i) + return ans +``` + +## 优秀解答 + +> 暂缺 diff --git a/daily/2019-06-08.md b/daily/2019-06-08.md new file mode 100644 index 0000000..19b8349 --- /dev/null +++ b/daily/2019-06-08.md @@ -0,0 +1,116 @@ +## 每日一题 - Top K Frequent Elements + +### 信息卡片 + +- 时间:2019-06-08 +- 题目链接:https://leetcode.com/problems/top-k-frequent-elements/description/ +- tag:`Hash Table` `Heap` + +### 题目描述 + +``` +Given a non-empty array of integers, return the k most frequent elements. + +Example 1: + +Input: nums = [1,1,1,2,2,3], k = 2 +Output: [1,2] + +Example 2: + + +Input: nums = [1], k = 1 +Output: [1] + +Note: + +You may assume k is always valid, 1 ≤ k ≤ number of unique elements. +Your algorithm's time complexity must be better than O(n log n), where n is +the array's size. +``` + +简单来说,此题要求找出一个数组中出现次数最多的前K个数。 + +### 参考答案 + +以下参考答案均是以Java语言实现,不过对其他语言,思路都是相同的。 + +题目要求时间复杂度必须比O(n log n)要好,第一种解法的复杂度是O(n),第二种解法由于使用了优先队列,时间复杂度要比O(n)略差,但仍然能AC,所以优先推荐第一种使用HashMap + 桶的解法。 + +#### 解法I:HashMap + 桶 + +* 用HashMap统计所有元素的出现频率 +* 将统计结果按照出现次数把对应元素放入以出现次数为基础的桶中 +* 按照从后往前的顺序从桶中取前K个元素便是答案 + +参考代码 +```java +class Solution { + public List topKFrequent(int[] nums, int k) { + if (nums == null || nums.length == 0 || k <= 0) return Collections.emptyList(); + + Map statisticMap = new HashMap<>(); + + for (int i : nums) statisticMap.put(i, statisticMap.getOrDefault(i, 0) + 1); + + // 记录出现次数和对应元素的桶 + List[] bucket = new List[nums.length + 1]; + + for (int i : statisticMap.keySet()) { + int frequency = statisticMap.get(i); + + if (bucket[frequency] == null) bucket[frequency] = new ArrayList(); + + bucket[frequency].add(i); + } + + List result = new ArrayList<>(); + + for (int i = bucket.length - 1; i >= 0; i--) { + if (bucket[i] == null) continue; + + if (result.size() >= k) break; + + result.addAll(bucket[i]); + } + + return result; + } +} +``` +#### 解法II:HashMap + PriorityQueue + +思路大致和前面相同,不过是将出现频率的排序交给了优先队列而已,在使用优先队列的时候给其提供了一个比较器,该比较器会对加入的元素自动排序,最后选择优先队列的前K的元素返回即可。 + +参考代码 +```java +class Solution { + public List topKFrequent(int[] nums, int k) { + if (nums == null || nums.length == 0 || k <= 0) return Collections.emptyList(); + + Map map = new HashMap<>(); + + for (int i : nums) map.put(i, map.getOrDefault(i, 0) + 1); + + // 优先队列 + PriorityQueue> pq = new PriorityQueue<>(new Comparator>() { + @Override + public int compare(Map.Entry e1, Map.Entry e2) { + return e2.getValue() - e1.getValue(); // 确保出现次数多的数排列在队列的前面 + } + }); + + for (Map.Entry entry : map.entrySet()) pq.add(entry); + + int i = 1; + List result = new ArrayList<>(); + + while (i++ <= k) result.add(pq.poll().getKey()); + + return result; + } +} +``` +### 其他优秀解答 + +本题基本上好的解答就是上面的两种办法,为了格式和其他同学的PR尽量一致,我在编写此PR的时候删去了我解题时写下的详细注释,需要看详细注释的同学请[移步此处的第347题](https://github.com/jsycdut/leetcode/tree/master/practice/leetcode/301-400) diff --git a/daily/2019-06-09.md b/daily/2019-06-09.md new file mode 100644 index 0000000..7217ea1 --- /dev/null +++ b/daily/2019-06-09.md @@ -0,0 +1,134 @@ +## 每日一题 - Regular Expression Matching + +### 信息卡片 + +- 时间:2019-06-09 +- 题目链接:https://leetcode.com/problems/regular-expression-matching/ +- tag:`String` `Dynamic Programming` `Backtracking` + +### 题目描述 + +``` +Given an input string (s) and a pattern (p), implement regular expression matching with support for '.' and '*'. + + +'.' Matches any single character. +'*' Matches zero or more of the preceding element. + + +The matching should cover the entire input string (not partial). + +Note: + + +s could be empty and contains only lowercase letters a-z. +p could be empty and contains only lowercase letters a-z, and characters +like . or *. + + +Example 1: + + +Input: +s = "aa" +p = "a" +Output: false +Explanation: "a" does not match the entire string "aa". + + +Example 2: + + +Input: +s = "aa" +p = "a*" +Output: true +Explanation: '*' means zero or more of the precedeng element, 'a'. +Therefore, by repeating 'a' once, it becomes "aa". + + +Example 3: + + +Input: +s = "ab" +p = ".*" +Output: true +Explanation: ".*" means "zero or more (*) of any character (.)". + + +Example 4: + + +Input: +s = "aab" +p = "c*a*b" +Output: true +Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore +it matches "aab". + + +Example 5: + + +Input: +s = "mississippi" +p = "mis*is*p*." +Output: false + +``` + +本题要求判断给出的字符串和对应的正则是否匹配,匹配返回true,否则返回false。 + +本题光从解题来看,可以使用api作弊,比如用Java字符串的matches方法就可以一步过这道题,本题老老实实做的话,就需要用到动态规划。基本思路是考察s和p任意从头到两个字符之间的匹配程度,有点像编辑距离那道题。 + +### 参考答案 + +```java +class Solution { + public boolean isMatch(String s, String p) { + if (s == null || p == null) return false; + + // dp[i][j]代表s的前i位字符和p的前j位字符的匹配程度 + // dp[1][2]代表s的第一个字符和p的前两个字符的匹配 + // 如果s的第i个字符和p的第j个相等或者j为.那么di[i][j]的匹配程度取决于dp[i - 1][j - 1] + // 如果p的第j个字符为*,那么就要考虑*匹配0个,1个,n个s的第i个字符的情况,以及根本匹配不了的情况 + // 根本匹配不了的意思是指p的j - 1个字符和s的第i个字符不相同,此时的匹配情况是dp[i][j] = dp[i][j - 2] + // 然后是p的第j - 1个字符是.或者和s的i个字符相同的情况,此时可以匹配0 1 个n个s的第i个字符 + // 0个: dp[i][j] = dp[i][j - 2] + // 1个: dp[i][j] = dp[i - 1][j - 1] + // n个: dp[i][j] = dp[i - 1][j] + // n个的最不好理解,可以按照下面的想 + // 我能匹配你n个,我肯定匹配了你前面的n-1个,也就是dp[i - 1][j] + // 最后的结果是dp[s.length()][p.length()] + boolean[][] dp = new boolean[s.length() + 1][p.length() + 1]; + + dp[0][0] = true; + + for (int j = 2; j <= p.length(); j++) { + if (p.charAt(j - 1) == '*' && dp[0][j - 2]) dp[0][j] = true; + } + + for (int i = 1; i <= s.length(); i++) { + for (int j = 1; j <= p.length(); j++) { + if (p.charAt(j - 1) == '.' || p.charAt(j - 1) == s.charAt(i - 1)) { + dp[i][j] = dp[i - 1][j - 1]; + } else if (p.charAt(j - 1) == '*') { + if (p.charAt(j - 2) != s.charAt(i - 1) && p.charAt(j - 2) != '.') { + dp[i][j] = dp[i][j - 2]; + } else { + dp[i][j] = (dp[i][j - 2] || dp[i][j - 1] || dp[i - 1][j]); + } + } + } + } + + return dp[s.length()][p.length()]; + } +} + +``` +### 其他优秀解答 +``` +暂无 +``` diff --git a/daily/2019-06-10.md b/daily/2019-06-10.md new file mode 100644 index 0000000..f866175 --- /dev/null +++ b/daily/2019-06-10.md @@ -0,0 +1,150 @@ +## 每日一题 - merge-two-binary-trees + +### 信息卡片 + +- 时间:2019-06-10 +- 题目链接:https://leetcode-cn.com/problems/merge-two-binary-trees/ +- tag:`tree` `Recursion ` + +### 题目描述 + +``` +Given two binary trees and imagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not. + +You need to merge them into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of new tree. + +Example 1: + +Input: + Tree 1 Tree 2 + 1 2 + / \ / \ + 3 2 1 3 + / \ \ + 5 4 7 +Output: +Merged tree: + 3 + / \ + 4 5 + / \ \ + 5 4 7 + + +Note: The merging process must start from the root nodes of both trees. +``` + +### 参考答案 + +#### 递归 +- 构造新tree的根节点 +- 递归构造新tree根节点左子树 +- 递归构造新tree根节点右子树 + +理解“树是一种递归的数据结构” + +Time complexity : O(n) +Space complexity : O(n) + +参考代码 +```javascript +/* + * @lc app=leetcode id=617 lang=javascript + * + * [617] Merge Two Binary Trees + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} t1 + * @param {TreeNode} t2 + * @return {TreeNode} + */ +var mergeTrees = function(t1, t2) { + // 递归,由于树是一种递归的数据结构,因此递归是符合直觉且比较简单的 + if (t1 === null) return t2; + if (t2 === null) return t1; + t1.val += t2.val; + t1.left = mergeTrees(t1.left, t2.left); + t1.right = mergeTrees(t1.right, t2.right); + return t1; +}; +``` + + + +### 其他优秀解答 + + +#### 迭代 + +层次遍历,利用数据结构是队列。 + + +参考代码 +```c++ +class Solution2 { +public: + TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) + { + //t2合并到t1上,t1必须存在,如果不存在就结束。 + if(t1 ==NULL) + { + return t2; + } + vector node(2); + node[0]=t1; + node[1]=t2; + + queue> data; + data.push(node); //第一次出队列的数据就是root节点. + + + while(!data.empty()) + { + //出队列操作 + vector temp=data.front(); + data.pop(); + + TreeNode*pt1=temp[0]; + TreeNode*pt2=temp[1]; + if(pt2==NULL) + { + continue;//维持pt1结构不变 + + } + //pt1如果null 是不入队列的。 + pt1->val+=pt2->val; // 结构不变,只修改节点数值 + // + if(pt1->left ==NULL) + { + pt1->left =pt2->left; //结构发生生变化,不能如队列。该节点遍历将结束。 + }else + { + node[0]=pt1->left; + node[1]=pt2->left; + data.push(node); //结构不变,可以入队列操作 + + } + + if(pt1->right ==NULL) + { + pt1->right =pt2->right; //结构发生生变化,不能如队列。该节点遍历将结束。 + }else + { + node[0]=pt1->right; + node[1]=pt2->right; + data.push(node); + + } + } + + return t1; + } +}; +``` diff --git a/daily/2019-06-11.md b/daily/2019-06-11.md new file mode 100644 index 0000000..a7bf1df --- /dev/null +++ b/daily/2019-06-11.md @@ -0,0 +1,188 @@ +## 每日一题 - 重复数据排序优化 + +### 信息卡片 + +- 时间:2019-06-11 +- tag:`Quike Sort` + +### 题目描述 + +``` +如果一个数组含有大量重复元素,我们应该选择什么样的排序方法,背后的理论依据是什么”? +``` + + +### 参考答案 + +取决于数据分布如何 + +1. 如果数据的总类很少, 而且每个都有大量重复的元素, 那么使用计数排序, 那么这个时间复杂度能够达到O(N). +```java +public class CountSort { + public int[] countSort(int[] array) { + int max = array[0]; + for(int i=1; imax) { + max = array[i]; + } + } + //创建计数数组 + int[] countArray = new int[max+1]; + for(int i=0; i 0) { + quickSort(a, 0, a.length - 1); + } + } + + private void swap(int[] arr, int a, int b) { + int temp = arr[a]; + arr[a] = arr[b]; + arr[b] = temp; + } + + private int choosePivotMedianOfThree(int[] a, int l, int r) { + int mid = 0; + if ((r-l+1) % 2 == 0) { + mid = l + (r-l+1)/2 - 1; + } else { + mid = l + (r-l+1)/2; + } + + // 只需要找出中位数即可,不需要交换 + if (((a[l]-a[mid]) * (a[l]-a[r])) <= 0) { + return l; + } else if (((a[mid]-a[l]) * (a[mid]-a[r])) <= 0) { + return mid; + } else { + return r; + } + } + + private void quickSort(int[] a, int left, int right) { + if (right <= left) + return; + + // 在数据近乎有序的时候, 插入排序的性能近乎于O(N) + if(right - left <= INSERTION_SORT_THRESHOLD) { + insertSort(a, left, right) + } + + /* + * 工作指针 + * p指向序列左边等于pivot元素的位置 + * q指向序列右边等于Pivot元素的位置 + * i指向从左向右扫面时的元素 + * j指向从右向左扫描时的元素 + */ + int p, q, i, j; + int pivot;// 锚点 + i = p = left; + j = q = right - 1; + + + /* + * 每次总是取序列最右边/最优和最中间的元素的大小中间值为锚点 + */ + pivot = choosePivotMedianOfThree(a, left, right); + + //始终将第一个元素作为pivot, 若不是, 则与之交换 + if (pivot != left) { + swap(a, pivot, left); + } + pivot = a[right]; + + while (true) { + /* + * 工作指针i从右向左不断扫描,找小于或者等于锚点元素的元素 + */ + while (i < right && a[i] <= pivot) { + /* + * 找到与锚点元素相等的元素将其交换到p所指示的位置 + */ + if (a[i] == pivot) { + swap(a, i, p); + p++; + } + i++; + } + /* + * 工作指针j从左向右不断扫描,找大于或者等于锚点元素的元素 + */ + while (left <= j && a[j] >= pivot) { + /* + * 找到与锚点元素相等的元素将其交换到q所指示的位置 + */ + if (a[j] == pivot) { + swap(a, j, q); + q--; + } + j--; + } + /* + * 如果两个工作指针i j相遇则一趟遍历结束 + */ + if (i >= j) + break; + + /* + * 将左边大于pivot的元素与右边小于pivot元素进行交换 + */ + swap(a, i, j); + i++; + j--; + } + /* + * 因为工作指针i指向的是当前需要处理元素的下一个元素 + * 故而需要退回到当前元素的实际位置,然后将等于pivot元素交换到序列中间 + */ + i--; + p--; + while (p >= left) { + swap(a, i, p); + i--; + p--; + } + /* + * 因为工作指针j指向的是当前需要处理元素的上一个元素 + * 故而需要退回到当前元素的实际位置,然后将等于pivot元素交换到序列中间 + */ + j++; + q++; + while (q <= right) { + swap(a, j, q); + j++; + q++; + } + + /* + * 递归遍历左右子序列 + */ + quickSort(a, left, i); + quickSort(a, j, right); + } +} +``` + diff --git a/daily/2019-06-13.md b/daily/2019-06-13.md new file mode 100644 index 0000000..71e911d --- /dev/null +++ b/daily/2019-06-13.md @@ -0,0 +1,30 @@ +## 每日一题 - 三门问题 (Three Doors Problem / Monty Hall problem) + +### 信息卡片 + +今天这道题不是算法题,而是一道概率论的经典问题,相当违反直觉,很有意思。题目描述如下: + +- 时间: 2019-06-13 +- tag: `Probability Theory` + +## 题目描述 + +``` +假设你在参加一个春节抽奖游戏,主持人在三个红包里面分别放了 1 块钱、1 块钱和 1000 块钱。你选中哪一个,你就可以领到对应的钱。当你选定一个红包之后,主持人会打开一个 1 块钱的红包,并给你一次机会更换所选红包。请问:应不应该换? +``` + +## 答案 + +**要换**。换了之后有 `2/3` 的概率拿到 1000 的红包。而不是直觉告诉我们的 `1/2`. + +这份[代码](./answers/three-doors-problem.js)进行了多次实验,验证了这一结论。 + +## 简要解释 + +这里的核心在于,主持人不是随机打开一个红包,而是挑选了一定是 1 块钱的那个红包。 + +## 详细解释 + +以下链接给出了数学证明和非常详细的解释:有兴趣可以看看。 + +[Monty Hall 问题](https://mp.weixin.qq.com/s?__biz=MzIzODExMDE5MA==&mid=445629202&idx=1&sn=451fe436511f2b00d2354e8dd074b7fa#rd) diff --git a/daily/2019-06-14.md b/daily/2019-06-14.md new file mode 100644 index 0000000..3624d51 --- /dev/null +++ b/daily/2019-06-14.md @@ -0,0 +1,158 @@ +## 每日一题 - flatten-binary-tree-to-linked-list + +### 信息卡片 + +- 时间:2019-06-14 +- 题目链接:https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/ +- tag:`tree` `Recursion ` + +### 题目描述 + +``` +Given a binary tree, flatten it to a linked list in-place. + +For example, given the following tree: + + 1 + / \ + 2 5 + / \ \ +3 4 6 +The flattened tree should look like: + +1 + \ + 2 + \ + 3 + \ + 4 + \ + 5 + \ + 6 +``` + +### 参考答案 + +#### 方法1 先序遍历 + +如果仔细观察输入输出的话会发现,其实输出其实就是输入的先序遍历结果而已。 +因此一种做法就是我们对其进行先序遍历, + +然后将先序遍历的结果构造成没有左子树的二叉树即可 + +Time complexity : O(n) +Space complexity : O(n) + +参考代码 +```javascript +/* + * @lc app=leetcode id=114 lang=javascript + * + * [114] Flatten Binary Tree to Linked List + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function preorderTraversal(root) { + if (!root) return []; + + return [root] + .concat(preorderTraversal(root.left)) + .concat(preorderTraversal(root.right)); +} +/** + * @param {TreeNode} root + * @return {void} Do not return anything, modify root in-place instead. + */ +var flatten = function(root) { + if (root === null) return root; + const res = preorderTraversal(root); + + let curPos = 0; + let curNode = res[0]; + + while(curNode = res[curPos]) { + curNode.left = null; + curNode.right = res[++curPos]; + } +}; +``` + + + +### 其他优秀解答 + +#### 方法2 先序遍历优化(递归遍历和非递归遍历) +算法描述 + + 把一颗二叉树变成单链表 flatten(root) + +- 递归遍历把一棵树左子树变成单链表 a +- 递归遍历把一棵树右子树变成单链表 b +- 用链表a最后一个元素拼接链表b(递归子问题) + +参考代码 + +- 递归 +```c++ + + void flatten(TreeNode* root) { + if (root == NULL) return ; + + flatten(root->left); + flatten(root->right); + + //递归子问题 + TreeNode *tmp = root->right; + root->right = root->left; + root->left = NULL; + + while (root->right) + { + root = root->right; + }; + + root->right = tmp; + } + }; +``` + +- 非递归 +```c++ + + void flatten(TreeNode* root) { + if (root == NULL) { + return ; + } + stack result; + result.push(root); + + while (!result.empty()){ + TreeNode* cur=result.top(); + result.pop(); + + if (cur->right) + { + result.push(cur->right);//先顺非递归遍历 + } + + if (cur->left) + { + result.push(cur->left);//先顺非递归遍历 + } + //递归子问题 + if (!result.empty()) + { + cur->right=result.top(); + } + cur->left=NULL; + } + + +``` diff --git a/daily/2019-06-17.md b/daily/2019-06-17.md new file mode 100644 index 0000000..10be8ef --- /dev/null +++ b/daily/2019-06-17.md @@ -0,0 +1,84 @@ +# 毎日一题 - 744. find smallest letter greater than target + +## 信息卡片 +* 时间:2019-06-17 +* 题目链接:https://leetcode.com/problems/find-smallest-letter-greater-than-target/ +* tag:`Array` + +## 题目描述 +``` +Given a list of sorted characters letters containing only lowercase letters, and given a target letter target, find the smallest element in the list that is larger than the given target. + +Letters also wrap around. For example, if the target is target = 'z' and letters = ['a', 'b'], the answer is 'a'. + +Examples: + Input: + letters = ["c", "f", "j"] + target = "a" + Output: "c" + + Input: + letters = ["c", "f", "j"] + target = "c" + Output: "f" + + Input: + letters = ["c", "f", "j"] + target = "d" + Output: "f" + + Input: + letters = ["c", "f", "j"] + target = "g" + Output: "j" + + Input: + letters = ["c", "f", "j"] + target = "j" + Output: "c" + + Input: + letters = ["c", "f", "j"] + target = "k" + Output: "c" +Note: + letters has a length in range [2, 10000]. + letters consists of lowercase letters, and contains at least 2 unique letters. + target is a lowercase letter. +``` + +## 思路 +二分查找,提高速度 +要求是查找某一个元素,又是在有序的集合中。 +所以我们可以用二分查找 +1. 排除两种情况;target 小于首元素|| target 大于等于尾元素 => 目标都是首元素 +2. 当target>=letters[mid] 时(我们要的值一定在右边),调整左区间 min = mid+1; +3. 当target< letters[mid] 时,调整右区间 max = mid-1; +4. 循环终止条件是 min > max; 最终返回min位置元素 + +## 建议 +在leetcode上找一个数组稍微长一点的测试用例,在纸上画出整个过程;对理解很有帮助 + +## 参考答案 +```js +/** + * @param {character[]} letters + * @param {character} target + * @return {character} + */ +var nextGreatestLetter = function(letters, target) { + const length = letters.length + let min = 0; + let max = length - 1; + if(target >= letters[length-1] || target < letters[0]) return letters[0]; + while(min <= max) { + const mid = (max+min) >> 1 + if(target >= letters[mid]) { + min = mid + 1; + } else { + max = mid - 1; + } + } + return letters[min] +}; +``` diff --git a/daily/2019-06-18.md b/daily/2019-06-18.md new file mode 100644 index 0000000..ab5d577 --- /dev/null +++ b/daily/2019-06-18.md @@ -0,0 +1,39 @@ +## 每日一题 - Letter Combinations Of A Phone Number + +### 信息卡片 + +- 时间:2019-06-18 +- 题目链接:https://leetcode.com/problems/letter-combinations-of-a-phone-number/ +- tag:`backtrack` + +### 题目描述 + +``` +Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. + +A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters. + + + +Example: + +Input: "23" +Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. +Note: + +Although the above answer is in lexicographical order, your answer could be in any order you want. +``` + +### 参考答案 + +求解电话号码能够组成的所有可能性。由于要列举所有的,因此很容易想到的是全排列,而不是 DFS. +如果大家之前看过我仓库的话,应该知道这种题目有一个通用的解题框架和模板。 +今天我们就来巩固下这个解题思路。 题目简单直接我就不说了,如果没有明白的可以移步到我的仓库中继续看。 比如 https://github.com/azl397985856/leetcode/blob/master/problems/39.combination-sum.md 就用到了这种方法(回溯法) + +参考代码: https://github.com/azl397985856/leetcode/blob/master/daily/answers/17.letter-combinations-of-a-phone-number.js + +### 其他优秀解答 + +``` +暂无 +``` diff --git a/daily/2019-06-19.md b/daily/2019-06-19.md new file mode 100644 index 0000000..66ec497 --- /dev/null +++ b/daily/2019-06-19.md @@ -0,0 +1,51 @@ +## 每日一题 - Big Countries + +### 信息卡片 + +- 时间:2019-06-19 +- 题目链接:https://leetcode.com/problems/big-countries/ +- tag:`sql` + +### 题目描述 + +``` +There is a table World + ++-----------------+------------+------------+--------------+---------------+ +| name | continent | area | population | gdp | ++-----------------+------------+------------+--------------+---------------+ +| Afghanistan | Asia | 652230 | 25500100 | 20343000 | +| Albania | Europe | 28748 | 2831741 | 12960000 | +| Algeria | Africa | 2381741 | 37100000 | 188681000 | +| Andorra | Europe | 468 | 78115 | 3712000 | +| Angola | Africa | 1246700 | 20609294 | 100990000 | ++-----------------+------------+------------+--------------+---------------+ +A country is big if it has an area of bigger than 3 million square km or a population of more than 25 million. + +Write a SQL solution to output big countries' name, population and area. + +For example, according to the above table, we should output: + ++--------------+-------------+--------------+ +| name | population | area | ++--------------+-------------+--------------+ +| Afghanistan | 25500100 | 652230 | +| Algeria | 37100000 | 2381741 | ++--------------+-------------+--------------+ +``` + +### 参考答案 + +最基本的sql语句,没什么好讲的。 如果不会的话,说明对基础语法不熟。 + +参考代码: + +```sql +select name, population, area from World where area > 3000000 or population > 25000000 + +``` + +### 其他优秀解答 +``` +暂无 +``` diff --git a/daily/2019-06-20.md b/daily/2019-06-20.md new file mode 100644 index 0000000..52ba018 --- /dev/null +++ b/daily/2019-06-20.md @@ -0,0 +1,102 @@ +# 毎日一题 - 594. Longest Harmonious Subsequence + +## 信息卡片 +* 时间:2019-06-20 +* 题目链接:https://leetcode.com/problems/longest-harmonious-subsequence/ +* tag:`Array` + +## 题目描述 +``` +We define a harmounious array as an array where the difference between its maximum value and its minimum value is exactly 1. + +Now, given an integer array, you need to find the length of its longest harmonious subsequence among all its possible subsequences. + +Example 1: + + Input: [1,3,2,2,5,2,3,7] + Output: 5 + Explanation: The longest harmonious subsequence is [3,2,2,2,3]. +``` + +## 思路 +1. 将数组中的值作为一个对象中的属性,出现的次数就是属性值 +2. 属性差一的值相加,获取最大的,否则返回0; + +## 参考答案 +```js +/** + * @param {number[]} nums + * @return {number} + * 使用ES6中的Map + */ +var findLHS = function(nums) { + if(!nums.length) return 0; + const map = new Map(); + let max = 0; + for(let i = 0; i leetcode 上有一个相似的[题目](https://leetcode.com/problems/sqrtx/) +- tag:`binary search` `math` + +### 题目描述 + +``` +要求不用数学库,求 sqrt(2)精确到小数点后 10 位 +``` + +### 参考答案 + +1. 二分法 + +这个解法比较直接,就是普通的二分。 +通过每次取中间值进行比较,我们可以舍去一半的结果。时间复杂度logn + +参考代码: + +```js +function sqrt(num) { + if (num < 0) return num; + let start = 0; + let end = num; + let mid = num >> 1; + const DIGIT_COUNT = 10; + const PRECISION = Math.pow(0.1, DIGIT_COUNT); + while (Math.abs(+(num - mid * mid).toFixed(DIGIT_COUNT)) > PRECISION) { + mid = start + (end - start) / 2.0; + if (mid * mid < num) { + start = mid; + } else { + end = mid; + } + } + + return mid; +} +``` + +2. 牛顿迭代法 + +这种方法是牛顿发明的,比较巧妙。 +其实上述问题可以转化为x^2-a = 0,求x的值。其实也就是曲线和y轴交点的横坐标。 +我们可以不断用f(x)的切线来逼近方程 x^2-a = 0的根。 +根号a实际上就是x^2-a=0的一个正实根,由于这个函数的导数是2x。 +也就是说,函数上任一点(x,f(x))处的切线斜率是2x。 +那么,x-f(x)/(2x)就是一个比x更接近的近似值。代入 f(x)=x^2-a得到x-(x^2-a)/(2x),也就是(x+a/x)/2。 + +![2019-06-27](../assets/daily/2019-06-27.gif) + +(图片来自Wikipedia) + +参考代码: + +```js +function sqrtNewton(n) { + if (n <= 0) return n; + + let res; + let last; + const DIGIT_COUNT = 10; + const PRECISION = Math.pow(0.1, DIGIT_COUNT); + + res = n; + + while (Math.abs(last - res) > PRECISION) { + last = res; + res = (res + n / res) / 2; + } + + return res; +} +``` + +### 其他优秀解答 + +``` +暂无 +``` diff --git a/daily/2019-07-01.md b/daily/2019-07-01.md new file mode 100644 index 0000000..3011c94 --- /dev/null +++ b/daily/2019-07-01.md @@ -0,0 +1,24 @@ +## 每日一题 - deliver-medicine + +### 信息卡片 + +- 时间:2019-07-01 +- 题目链接:无 +- tag:`logic` + +### 题目描述 + +``` +A、B两人分别在两座岛上。B生病了,A有B所需要的药。C有一艘小船和一个可以上锁的箱子。C愿意在A和B之间运东西,但东西只能放在箱子里。只要箱子没被上锁,C都会偷走箱子里的东西,不管箱子里有什么。如果A和B各自有一把锁和只能开自己那把锁的钥匙,A应该如何把东西安全递交给B? + +``` + +### 参考答案 + +把药放在箱子,用自己的锁把箱子锁上。B拿到箱子后,再在箱子上加一把自己的锁。箱子运回A后,A取下自己的锁。箱子再运到B手中时,B取下自己的锁,获得药物。 + +### 其他优秀解答 + +``` +暂无 +``` \ No newline at end of file diff --git a/daily/2019-07-04.md b/daily/2019-07-04.md new file mode 100644 index 0000000..20bf81d --- /dev/null +++ b/daily/2019-07-04.md @@ -0,0 +1,96 @@ +## 每日一题 - longest-univalue-path + +### 信息卡片 + +- 时间:2019-07-04 +- 题目链接:https://leetcode.com/problems/longest-univalue-path/ +- tag:`recursive` `tree` + +### 题目描述 + +``` +Given a binary tree, find the length of the longest path where each node in the path has the same value. This path may or may not pass through the root. + +The length of path between two nodes is represented by the number of edges between them. + + + +Example 1: + +Input: + + 5 + / \ + 4 5 + / \ \ + 1 1 5 +Output: 2 + + + +Example 2: + +Input: + + 1 + / \ + 4 5 + / \ \ + 4 4 5 +Output: 2 + + + +Note: The given binary tree has not more than 10000 nodes. The height of the tree is not more than 1000. + +``` + +### 参考答案 + +```js +/* + * @lc app=leetcode id=687 lang=javascript + * + * [687] Longest Univalue Path + */ + +// 返回经过root的且只能取左右一个节点的路径长度 +function helper(node, res) { + if (node === null) return 0; + const l = helper(node.left, res); + const r = helper(node.right, res); + let lcnt = 0; + let rcnt = 0; + if (node.left && node.val === node.left.val) lcnt = lcnt + l + 1; + if (node.right && node.val === node.right.val) rcnt = rcnt + r + 1; + + res.max = Math.max(res.max, lcnt + rcnt); + + return Math.max(lcnt, rcnt); + } + /** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ + /** + * @param {TreeNode} root + * @return {number} + */ + var longestUnivaluePath = function(root) { + const res = { + max: 0 + }; + helper(root, res); + return res.max; + }; + +``` + +### 其他优秀解答 + +``` +暂无 +``` \ No newline at end of file diff --git a/daily/2019-07-08.md b/daily/2019-07-08.md new file mode 100644 index 0000000..a1a029f --- /dev/null +++ b/daily/2019-07-08.md @@ -0,0 +1,68 @@ +## 每日一题 - 赛马问题 + +### 信息卡片 + +- 时间:2019-07-08 +- 题目链接:无 +- tag:`dAc` + +### 题目描述 + +``` + +有25匹马,速度都不同,但每匹马的速度都是定值。现在只有5条赛道,无法计时,即每赛一场最多只能知道5匹马的相对快慢。 +问最少赛几场可以找出25匹马中速度最快的前3名? + +``` + +### 参考答案 + +七次。 + + +由于每一匹马我们都需要比赛才行,因此至少先比赛 25 / 5 = 5 次, +然后我们可以选择出来每一组的第一名,也就是一共5匹马,再进行一次比赛。这个时候跑第一名的一定是总体第一名。 + +我们来总结一下,这个时候我们已经决出了第一名,并且比赛了6次。 + +让我们来分析一下, 假如第六场比赛从第一名到第五名我们依次给其在第一场比赛的场次进行编队为A B C D E。 + +那么D E所在的一共 5 + 5 = 10 匹马是没有比赛的必要的, 不可能是前三。 + +C中的只有第一名可能是前三,其他四个我们可以直接舍弃。 + +B中有可能前三的只有一二名。 + +A中的二三名也可能是前三。 + +那么我们只需要把有可能成为前三的 A 中的 2 个, B 中的一个, 以及 C 中 一个 比一下就好了。五个刚好需要一次。 + +因此一共需要七次。 + + + +我们从分治的角度考虑一下, 也就是说怎么将其抽象为一般问题,就是转化为程序。 + +将原问题表示为f, 那么f(25, 5, 3) 表示25匹马,5个跑道,决出前三。 + +那么原问题可以转化为: + +``` +f(25, 5, 3) = 25 / 5 + f(5,5,1) + f(5,5,2) + +``` + +那么如果换成10匹马: + +``` +f(10, 5, 3) = 10 / 5 + f(5,5,1) + f(4,5,2) + +``` + +更为精确的代码我就不写了,大家可以自己思考一下。 + +### 其他优秀解答 + +``` +暂无 +``` \ No newline at end of file diff --git a/daily/2019-07-10.md b/daily/2019-07-10.md new file mode 100644 index 0000000..013bfdb --- /dev/null +++ b/daily/2019-07-10.md @@ -0,0 +1,50 @@ +## 每日一题 - 称球问题 + +### 信息卡片 + +- 时间:2019-07-10 +- 题目链接:无 +- tag:`math` + +### 题目描述 + +``` + +12个小球,其中有一个是坏球。有一架天平。需要你用最少的称次数来确定哪个小球是坏的并且它到底是轻还是重。 + +``` + +### 参考答案 + +3次。 + +我们先来分析一下: + + +由于天平的输出结果有三种“平衡、左倾、右倾”,这就相当于我们的问题有三个答案,即可以将所有的可能性切成三份, +根据猜数字游戏的启发,我们应当尽量让这三个分支概率均等,即平均切分所有的可能性为三等份。 +如此一来的话一次称量就可以将答案的可能性缩减为原来的1/3,三次就能缩减为1/27。而总共才有24种可能性,所以理论上是完全可以3次称出来的。 + + +这个题目解释起来比较费劲,我在网上找了一个现成的图来解释一下: + +![weight-ball](../assets/daily/weight-ball.jpg) + +图中“1+”是指“1号小球为重”这一可能性。“1-”是指“1号小球为轻”这一可能性。 +一开始一共有24种可能性。 + +4、4称了之后不管哪种情况(分支),剩下来的可能性总是4种。这是一个完美的三分。 + +然后对每个分支构造第二次称法,这里你只要稍加演算就可以发现,分支1上的第二次称法,即“1、2、6对3、4、5”这种称法,天平输出三种结果的可能性是均等的(严格来说是几乎均等)。 + +这就是为什么这个称法能够在最坏的情况下也能表现最好的原因,没有哪个分支是它的弱点,它必然能将情况缩小到原来的1/3。 + +### 其他优秀解答 + +``` +暂无 +``` + +### 参考 + +- [学之美番外篇:快排为什么那样快](http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/) \ No newline at end of file diff --git a/daily/2019-07-15.md b/daily/2019-07-15.md new file mode 100644 index 0000000..96adf87 --- /dev/null +++ b/daily/2019-07-15.md @@ -0,0 +1,49 @@ +## 每日一题 - 圆桌一先一后 + +### 信息卡片 + +- 时间: 2019-07-15 +- 题目链接:暂无 +- tag:`逻辑思维` + +### 题目描述: +``` +考虑一个双人游戏。游戏在一个圆桌上进行。每个游戏者都有足够多的硬币。他们需要在桌子上轮流放置硬币,每次必需且只能放置一枚硬币,要求硬币完全置于桌面内(不能有一部分悬在桌子外面),并且不能与原来放过的硬币重叠。谁没有地方放置新的硬币,谁就输了。游戏的先行者还是后行者有必胜策略?这种策略是什么? +``` + + +### 参考答案 +思路如下: + +首先,谁有必胜机会? +假设硬币跟桌子一样大,必然是先手者胜,所以这题的第一问答案必然是先手必胜 + +再假设硬币的大小是圆桌的“微分”,一个硬币就一个点的大小,那么桌子上就可以放下∞个硬币,但是因为圆桌本身是个圆,而圆关于圆心对称,所以一定是奇数个点,多出来的这个点是作为对称中心的圆心,再次印证结论先手必胜。 + +必胜的策略,先手抢圆心,之后保持和对方放置的硬币关于圆心对称即可 + + +### 扩展 +**扩展一:** + +有1996个棋子,两人轮流取棋子,每次允许取其中的2个,4个或8个, +谁最后取完棋子,就算获胜.那么先取的人为保证获胜,第一次应取几个棋子? + +**参考答案:** + +1. 1996这一类的问题其实1996和11992关系不大,先记为M,重要的是可选的{a,b,c...}这些选项 +2. 将选项集合记为 K={a,b,c..},在对方报出A∈K后,必有B∈K使得A+B = n * γ(n是正整数),本题中γ为6 +3. 确定好γ以后,剩下要做的事情就是M对γ取余,本题中M%γ=332余4 +4. 4是{abc...}里的一个选项,所以先手取4个棋子必胜 + +**扩展二:** + +在一个4×5的棋盘中,甲,乙两人轮流往棋盘的方格中放棋子,甲先放第一枚棋子,乙只能在与这枚棋子的相邻的格内放棋子(相邻是指有公共边的两个格),甲再放时又必须在乙刚放的棋子的相邻的格内放,以后照此规则放,谁无法放棋子的时候谁失败,如果都按最佳方案,谁取胜? + +**扩展三:** + +上述的题目全部变成变量,然后用代码写出来 +### 其他优秀解答 +``` +暂无 +``` diff --git a/daily/2019-07-18.md b/daily/2019-07-18.md new file mode 100644 index 0000000..9bebe4f --- /dev/null +++ b/daily/2019-07-18.md @@ -0,0 +1,92 @@ +## 每日一题 - squares-of-a-sorted-array + +### 信息卡片 + +- 时间:2019-07-18 +- 题目链接:https://leetcode.com/problems/squares-of-a-sorted-array/ +- tag:`Array` `Two Pointers` + +### 题目描述 + +``` +Given an array of integers A sorted in non-decreasing order, return an array of the squares of each number, also in sorted non-decreasing order. + + + +Example 1: + +Input: [-4,-1,0,3,10] +Output: [0,1,9,16,100] +Example 2: + +Input: [-7,-3,2,3,11] +Output: [4,9,9,49,121] + + +Note: + +1 <= A.length <= 10000 +-10000 <= A[i] <= 10000 +A is sorted in non-decreasing order. + +``` + +### 思路 + +典型的双指针问题。我们记录头尾指针, +然后每次`移动两个指针指向的值中绝对值较大的那个`就好了。 + +这个很好理解,因为是从小到大排列,我们可以获取到最小的元素和最大的元素。 +平方较大的元素一定是最小的元素或者最大的元素,因此我们两个指针指向首尾就好了。 + +更新的策略也很简单,由于我们取得的绝对值是从大到小的,因此我们新建一个数组, +然后从后面往前放就好了。 + +### 参考答案 + +```js + +/* + * @lc app=leetcode id=977 lang=javascript + * + * [977] Squares of a Sorted Array + */ +/** + * @param {number[]} A + * @return {number[]} + */ +var sortedSquares = function(A) { + let start = 0; + let end = A.length - 1; + const res = []; + let cur = 0; + + while (start <= end) { + if (Math.abs(A[start]) === Math.abs(A[end])) { + cur++; + res[A.length - cur] = A[start] * A[start]; + cur++ + res[A.length - cur] = A[end] * A[end]; + start++; + end--; + } else if (Math.abs(A[start]) > Math.abs(A[end])) { + cur++; + res[A.length - cur] = A[start] * A[start]; + start++; + } else { + cur++; + res[A.length - cur] = A[end] * A[end]; + end--; + } + } + + return res; +}; + + +``` + +### 其他优秀解答 +``` +暂无 +``` diff --git a/daily/2019-07-19.md b/daily/2019-07-19.md new file mode 100755 index 0000000..f6fd8e2 --- /dev/null +++ b/daily/2019-07-19.md @@ -0,0 +1,36 @@ +# 毎日一题 - 洗牌算法 + +## 信息卡片 + +* 时间:2019-07-19 +* 题目链接:暂无 +* tag:`Array` `Probability` + +## 题目描述 + +``` +假设我们有一个n个元素的数组,要求你实现一个函数,该函数会随机地返回n个元素的排列,要求所有排列出现的概率是一样的。即每一个排列出现的概率都是1/n!. +``` +## 参考答案 +思路如下 + +像洗牌一样,从数组中随机取出一个,放入另一个全新的数组中,但这会涉及到数组删除操作. +在这个基础上转换一下思路把从数组中取出的元素放入原数组中,第一次随机删除时,把它与原数组的倒数第一个交换,第2次在剩下的元素中随机删除时,把它与原数组的倒数第2个交换,第n-1次(最后一次不用换)时便完成了洗牌 时间复杂度为O(n) + +```js + function shuffle(list) { + for (let i = list.length - 1; i >= 1; i--) { + const random = (Math.random() * (i + 1)) >> 0; + const temp = list[i]; + list[i] = list[random]; + list[random] = temp; + } + } +``` +注: 概率证明, 任意一个元素放在倒数第一个位置的概率为1/n,放到倒数第2个的概率为 [(n-1)/n ]* [1/(n-1)] = 1/n,放在倒数第k个位置的概率是[(n-1)/n] * [(n-2)/(n-1)] *...* [(n-k+1)/(n-k+2)] *[1/(n-k+1)] = 1/n, 因此每一个元素放在任意位置的概率都为1/n,所有的排列出现的概率则为 1/n * 1/(n-1) *..* 1 = 1/n! + +注:在交换时,之所以第一次与第n个交换不与第一个交换,是因为与第n个交换代码更简洁 + +## 优秀解答 + +>暂缺 \ No newline at end of file diff --git a/daily/2019-07-22.md b/daily/2019-07-22.md new file mode 100644 index 0000000..7bc8d9a --- /dev/null +++ b/daily/2019-07-22.md @@ -0,0 +1,91 @@ +# 毎日一题 - 524.longest-word-in-dictionary-through-deleting + +## 信息卡片 + +- 时间:2019-07-22 +- 题目链接:https://leetcode-cn.com/problems/longest-word-in-dictionary-through-deleting/ +- tag:`String` `Two Pointers` + +## 题目描述 + +``` +给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。 + +示例 1: + +输入: +s = "abpcplea", d = ["ale","apple","monkey","plea"] + +输出: +"apple" +示例 2: + +输入: +s = "abpcplea", d = ["a","b","c"] + +输出: +"a" +说明: + +1. 所有输入的字符串只包含小写字母。 +2. 字典的大小不会超过 1000。 +3. 所有输入的字符串长度不会超过 1000。 + +``` + +## 参考答案 + +我们的思路是删除字符串中的某些字符,使得可以组成数组中的字符串, +然后我们找到最长。 + +`字符串删除某些字符,使之成为另一个字符串`,这本质上是字符串子序列问题。 + +求解 subSequence 的题目,可以用双指针解决 +比如在字符串 a 中查找 b,那么快指针在 a 上,慢指针在 b。 快指针一直更新,慢指针只有两个相等才更新 +最后比较慢指针是否走到底了即可 + + +参考JavaScript代码: + + +```js + +function isSequence(s, word) { + if (word.length > s.length) return false; + + let i = 0; + let j = 0; + + while(i < s.length && j < s.length) { + if (word[i] === s[j]) i++; + j++; + } + + // 说明有s中有word.length个元素和word匹配(且顺序一致) + // 换句话说就是word是s的子序列 + return i === word.length; +} +/** + * @param {string} s + * @param {string[]} d + * @return {string} + */ +var findLongestWord = function(s, d) { + let res = ""; + + for (let word of d) { + if (isSequence(s, word)) { + if (word.length > res.length) res = word; + else if (word.length === res.length && word.charAt(0) < res.charAt(0)) + res = word; + } + } + + return res; +}; + +``` + +## 优秀解答 + +> 暂缺 diff --git a/daily/2019-07-23.md b/daily/2019-07-23.md new file mode 100644 index 0000000..e8efd37 --- /dev/null +++ b/daily/2019-07-23.md @@ -0,0 +1,28 @@ +# 毎日一题 - 删除没有头节点的单链表中的指定项 + +## 信息卡片 + +- 时间:2019-07-23 +- 题目链接:无(来自编程之美) +- tag:`Linked List` + +## 题目描述 + +``` +假设有一个没有头指针的单链表,一个指针指向该单链表中间的一个节点(不是第一个,也不是最后一个节点), +请将该节点从单链表中删除。 + +``` + +![2019-07-23](../assets/daily/2019-07-23.jpeg) + +## 参考答案 + + +我们可以“移花接木”, 将要删除的节点的后面的节点的值给当前节点,然后删除后面的节点即可。 + + + +## 优秀解答 + +> 暂缺 diff --git a/daily/2019-07-24.md b/daily/2019-07-24.md new file mode 100644 index 0000000..84fd7eb --- /dev/null +++ b/daily/2019-07-24.md @@ -0,0 +1,30 @@ + # 毎日一题 - 灯泡问题 + +## 信息卡片 + +- 时间:2019-07-24 +- 题目链接:无 +- tag:`发散思维` + +## 题目描述 + +``` +房子有三盏灯,屋外有三个开关,分别控制这三盏灯,只有进去房间,才能看到哪一个灯是亮的。 +请问如何只进一次房间,就能指明哪一个开关控制哪一个灯? + +``` + +## 参考答案 + +这个问题比较发散,下面的答案仅供参考: + +1. 首先你应该问这个灯是什么样的灯 +2. 如果是电阻比较大的灯,根据焦耳定律,会有相对比较大的发热 +但是发热 Q = I ^ 2 * R * t, 因此发热量除了和电阻有关,其实和电流和t都有关系, +如何评估这种差异,寻找到人能感受到的热量差异,需要面试者自己去探索和分析 + + + +## 优秀解答 + +> 暂缺 diff --git a/daily/2019-07-25.md b/daily/2019-07-25.md new file mode 100644 index 0000000..04a43e1 --- /dev/null +++ b/daily/2019-07-25.md @@ -0,0 +1,92 @@ +# 毎日一题 - 9. Palindrome number + +## 信息卡片 + +- 时间:2019-07-25 +- 题目链接:https://leetcode.com/problems/palindrome-number/submissions/ +- tag:`Math` + +## 题目描述 + +``` +Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same backward as forward. + +Example 1: + + Input: 121 + Output: true + +Example 2: + + Input: -121 + Output: false + Explanation: From left to right, it reads -121. From right to left, it becomes 121-. Therefore it is not a palindrome. + +Example 3: + + Input: 10 + Output: false + Explanation: Reads 01 from right to left. Therefore it is not a palindrome. + +Follow up: + + Coud you solve it without converting the integer to a string? +``` + +## 参考答案 + +转成字符串方式 +1. 负数都是非回文数,10的整数倍不是回文。 +2. 将数字转为字符串,再逆序排列字符串。两者比较,相等就是回文数。 + +直接操作整数方式 +1. 复制x到temp; +2. 取temp末尾数字,方式为temp与10的求余;组成新数reverse; +3. 每取完一位,temp缩小10倍并且去掉小数。 +4. reverse要`先扩大十倍`再加上取下来的数 +5. 当temp === 0时,表示已经取完;reverse与x比较 + + +参考JavaScript代码: + +```js +/** + * @param {number} x + * @return {boolean} + * 转成字符串 + */ +var isPalindrome = function(x) { + if(x < 0 ) return false; + if(x === 0) return true; + if(x % 10 === 0) return false; + let reverse = ''; + let str = String(x); + for(let i = str.length - 1; i >= 0; i--) { + reverse += str[i] + } + return str === reverse +}; + +/** + * @param {number} x + * @return {boolean} + * 不转成字符串 + */ +var isPalindrome = function(x) { + if(x < 0 ) return false; + if(x === 0) return true; + if(x % 10 === 0) return false; + let temp = x; + let reverse = 0; + while(temp > 0) { + let num = temp % 10; + temp = (temp - num)/10; // 或 temp = (temp / 10) >> 0,去除小数位 + reverse = reverse * 10 + num + } + return x === reverse +}; +``` + +## 优秀解答 + +> 暂缺 diff --git a/daily/2019-07-26.md b/daily/2019-07-26.md new file mode 100644 index 0000000..e3cf1b3 --- /dev/null +++ b/daily/2019-07-26.md @@ -0,0 +1,60 @@ +# 毎日一题 - 将帅问题 + +## 信息卡片 + +- 时间:2019-07-26 +- 题目链接:无(来自编程之美) +- tag:`数据压缩` + +## 题目描述 + +![2019-07-26](../assets/daily/2019-07-26.jpeg) + +## 参考答案 + +这是数据压缩问题中的一种。 + +类似的问题有, 如何将 IP 地址用 4 个字节来表示等等。 + +这道题的思路,如果我们不考虑用一个字节去存储的话,我们通过观察 +坐标,发现“坐标和 3 取余的结果相同的就是同一列”,因此我们可以根据 +这个来判断位置是否合法。 + +我们容易写出类似下面的代码: + +```js +for (let i = 0; i < 9; i++) { + for (let j = 0; j < 9; j++) { + if (i % 3 !== j % 3) { + console.log(`${i + 1}, ${j + 1}`); + } + } +} +``` + +可以看出上面的写法用到了两个字节去表示,如何将上面的写法压缩到一个字节呢? + +仔细观察我们发现,内存循环和外层循环的长度是一样的, +其实我们内外循环用一个变量表示。 + + +内外循环总共执行了81次。 我们定义一个变量为81. +然后用i / 9 来表示外层循环的值。 用 i % 9 来表示内层循环的值。 + +可以看出,i增加9次之后,内存循环会增加9,外层增加1,整个过程类似上面。 + +代码如下: + +```js +let i = 81; + +while (i-- > 0) { + if (((i / 9) >> 0) % 3 !== (i % 9) % 3) { + console.log(`${((i / 9) >> 0) + 1}, ${(i % 9) + 1}`); + } +} +``` + +## 优秀解答 + +> 暂缺 diff --git a/daily/2019-07-29.md b/daily/2019-07-29.md new file mode 100644 index 0000000..a0899ca --- /dev/null +++ b/daily/2019-07-29.md @@ -0,0 +1,144 @@ +# 毎日一题 - 54.Spiral Matrix + +## 信息卡片 + +- 时间:2019-07-29 +- 题目链接:https://leetcode.com/problems/spiral-matrix/ +- tag:`Array` `Matrix` + +## 题目描述 + +``` +Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral order. + +Example 1: + + Input: + [ + [ 1, 2, 3 ], + [ 4, 5, 6 ], + [ 7, 8, 9 ] + ] + Output: [1,2,3,6,9,8,7,4,5] +Example 2: + + Input: + [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9,10,11,12] + ] + Output: [1,2,3,4,8,12,11,10,9,5,6,7] +``` + +## 参考答案 + +1. 剥洋葱,row->col->row->col 为一次; +2. row->col、col->row 的切换都伴随读取的初始位置的变化; +3. 结束条件是row头>row尾或者col顶>col底 + +![剥洋葱](../assets/problems/54.spiral-matrix.jpg) + +时间复杂度O(m*n), 空间复杂度O(1) + +参考JavaScript代码: + +```js +/** + * @param {number[][]} matrix + * @return {number[]} + */ +var spiralOrder = function(matrix) { + if(matrix.length === 0) return []; + let rowT = 0; // 行顶 + let rowB = matrix.length - 1; // 行底 + let colL = 0; // 列左 + let colR = matrix[0].length - 1; // 列右 + let result = []; + // 顺序是行、列、行、列;每次切换,读取的初始位置都会变化1(+/- 1) + while (colL <= colR && rowT <= rowB) { + for (let a = colL; a <= colR; a++) { + result.push(matrix[rowT][a]); + } + rowT++; + for (let b = rowT; b <= rowB; b++) { + result.push(matrix[b][colR]); + } + colR--; + for (let c = colR; c >= colL && rowB >= rowT; c--) { + result.push(matrix[rowB][c]); + } + rowB--; + for (let d = rowB; d >= rowT && colR >= colL; d--) { + result.push(matrix[d][colL]); + } + colL++; + } + return result; +}; +``` + +代码只有一个for循环的方式,操作方向 +例如 +> 1 2 3 4 5 +> 6 7 8 9 10 +> 11 12 13 14 15 +> +> 对上面矩阵遍历时的操作 +> +> 向右5次(算上从左侧第一次进入) +> 向下2次 +> 向左4次 +> 向上1次 +> 向右3次 +> 向下0次 -- 结束 + +方向有四个,right、down、left、up +四个方向又分两类,水平(right,left)和垂直(down,up) +而在两类方向上的移动最值是 水平n, 垂直m; +在遍历过程中,根据`方向切换`来减小n/m从而缩小两类方向的移动最值直到结束 +四个方向可以用二维数组来表示[ [0, 1], [1, 0], [0, -1], [-1, 0] ] +两类方向各自的初始最大值是[n, m-1] +当 n == 0 || m == 0 表示元素已经全部遍历完 + +这种写法省去了代码中的for循环,但是while循环次数却增多了;复杂度没有变化 +时间复杂度O(m*n), 空间复杂度O(1) + +参考JavaScript代码: + +```js +/** + * @param {number[][]} matrix + * @return {number[]} + * 一个for循环,但while变多了 + */ +var spiralOrder = function(matrix) { + if(matrix.length === 0) return []; + let m = matrix.length; + let n = matrix[0].length; + let result = []; + const dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]] // 控制方向的数组 + // 元素坐标row,col; + let row = 0; + let col = -1; + let steps = [n, m-1] + let dir = 0; // 初始方向 + while(steps[dir%2]) { + for(let i = 0; i < steps[dir%2]; i++) { + // 方向的改变的效果,row/col能增能减 + row += dirs[dir][0]; col += dirs[dir][1]; + result.push(matrix[row][col]) + } + steps[dir%2]--; // 移动极值缩小 + dir = (dir+1)%4; // 方向改变 + } + return result; +}; +``` + +## 优秀解答 + +> 暂缺 + +## 参考 +- @stellari [A concise C++ implementation based on Directions](https://leetcode.com/problems/spiral-matrix/discuss/20573/A-concise-C%2B%2B-implementation-based-on-Directions) diff --git a/daily/2019-07-30.md b/daily/2019-07-30.md new file mode 100644 index 0000000..c06fd32 --- /dev/null +++ b/daily/2019-07-30.md @@ -0,0 +1,32 @@ +## 每日一题 - 走地球问题 + +### 信息卡片 + +- 时间: 2019-07-30 +- 题目链接:暂无 +- tag:`几何` + +### 题目描述: + +``` +地球上有多少个点,使得从该点出发向南走一英里,向东走一英里,再向北走一英里之后恰好回到了起点? +``` + +### 参考答案 +无数个点 + +思路如下: +首先可以确定的是北极点(从北极点出发,任何角度都是向南) + +将地球看成一个标准球体,那么纬线就是无数个长度不等的圆,必然存在纬线满足周长等于`2πkR=1(英里) 其中k为正整数`,即半径为`R=1/2πk`的圆 +那么沿着这条纬线(记为E纬线)上任意一点向东走一英里,始终会回到原点,只是走的圈数不同而已。 +根据题目倒推,在这条纬线以北一英里存在一条纬线(记为N纬线),从N纬线的任意一点向南一英里到达E纬线W点,沿着E纬线向东一英里,必会回到W点,再向北走一英里恰好可以回到起点。北极点可能包含在这个集合中,也可能不在。 +如下图示供参考: +![earth-problem](../assets/daily/2019-07-30.jpg) + +所以答案是无数个点 + +### 其他优秀解答 +``` +暂无 +``` diff --git a/daily/2019-07-31.md b/daily/2019-07-31.md new file mode 100644 index 0000000..34d0c30 --- /dev/null +++ b/daily/2019-07-31.md @@ -0,0 +1,149 @@ +## 每日一题 - 小飞电梯调度问题 + +### 信息卡片 + +- 时间: 2019-07-31 +- 题目链接:暂无 +- tag:`Math` `Dynamic Programming` + +### 题目描述: + +``` +微软亚洲研究所所在的希格玛大厦一共有6部电梯。在高峰时间,每层都有人上下,电梯在每层都停。实习生小飞常常会被每层都停的电梯弄得很不耐烦,于是他提出了这样一个办法: +由于楼层并不太高看没在繁忙的上下班时间,每层电梯从一层往上走时,我们只允许电梯停在其中的某一层。所有的乘客都从一楼上电梯,到达某层楼后,电梯停下来,所有乘客再从这里爬楼梯到自己的目的层。 +在一楼的时候,每个乘客选择自己的目的层,电梯则自动计算出应停的楼层。 +问:电梯停在哪一层楼,能够保证这次乘坐电梯的所有乘客爬楼梯的层数之和最少。 + +扩展: + +1.如何在O(n)的时间复杂度完成? +2.往上爬楼梯,总是比往下走要累的。假设往上爬一个楼层,要耗费k单位的能量,而往下走只需要耗费1单位的能量,那么如果题目条件改为让所有人消耗的能量最少,这个问题怎么解决呢? +这个问题可以用类似上面的分析方法来解答看,因此笔者不再累述,留给读者自行解决。 +3.在一个高楼里面,电梯只在某一个楼层停,这个政策还是不太人性化。如果电梯会在k个楼层停呢?读者可以发挥自己的想象力,看看如何寻找最优方案。 +``` + +### 参考答案 + + +题意是 +每层都停 => 只停一层,其余让人爬楼梯;所有人爬梯之和最小 +选择目的层(i),在i层下的人数是T[i],根据大家选择的目的层计算在哪一层(X)停最优 +sum(1~N){T[i]*|i-x|}的最小值 + +从简单易想到的方式开始; +从1楼开始直到顶层,算出在每层人需要爬梯的总和数组result +找出Min(result)下标 +时间复杂度是O(N^2) + +```js +/** +* 两个测试数据 +* nPerson = [0, 1, 3, 4, 2, 3] +* nPerson = [0, 1, 0, 2, 2, 6] +*/ +function original(nPerson) { // nPerson首元素设0,使楼层与下标对应 + // nPerson[i] 在i层下的人, N 总楼层 + let result = [0]; // 存各层结果 + let target = 1; // 最小值下标 + for(let x = 1; x < nPerson.length; x++) { // 目标楼层x + result[x]=0; + for(let i = 1; i < nPerson.length; i++) { // 人在哪层停留 + result[x] += nPerson[i]*Math.abs(x-i); + } + if(result[target] > result[x]) { + target = x + } + } + return target; +} +``` + +进一步考虑(动态规划) +假设在i层停,共需要爬Y阶;在i层有N2人,在i层以下共N1人,i层以上共N3人 +如果在i-1层停,相比i层变化Y+N2+N3-N1 = Y - (N1-N2-N3) => N1 > (N2 + N3)时会减少爬阶数 +如果在i+1层停,相比i层变化Y-N3+N2+N1 = Y - (N3-N2-N1) => N3 > (N2 + N1)时会减少爬阶数 +所以在N1 > N2+N3时应该在i-1层停,N3 > N2+N1时应该在i+1层停; 否则在i层停 + +初始状态电梯停在第一层,向上进行状态的变迁,开始时N2 + N1 - N3 < 0 +sum越来越小,直到某一层N2 + N1 >= N3,就没有必要在往上走了。这时已求出最合适的楼层了 + +```js +function betterOne(nPerson) { // 首元素设空, 下标就与楼层对应了,nPerson的长度-1就是楼层数 + let N1 = 0; + let N2 = nPerson[1]; + let N3 = 0; + let target = 1; + // 第一层时,算出人需要走的楼梯数Y和在一楼以上的人数N3 + for(let i = 2; i < nPerson.length; i++) { + N3 += nPerson[i]; + } + // 再来优化 + for(let i = 2; i < nPerson.length; i++) { + if (N1+N2 < N3) { // 在i+1层停较优 + target = i; + N1 += N2 + N3 -= nPerson[i] + N2 = nPerson[i] + } else { + break + } + } + return target +} +``` + +扩展问题2的解 +向上爬比向下走更耗费体力,假设上楼是下楼耗费能量的k倍;k大于1 +比较消耗能量的大小决定楼层,只需在动态规划方式上增加权重即可 + +```js +function betterOnewithWeight(nPerson, k) { // 首元素设空, 下标就与楼层对应了,nPerson的长度-1就是楼层数 + let N1 = 0; + let N2 = nPerson[1]; + let N3 = 0; + let target = 1; + // 第一层时,算出人需要走的楼梯数Y和在一楼以上的人数N3 + for(let i = 2; i < nPerson.length; i++) { + N3 += nPerson[i]; + } + // 再来优化 + for(let i = 2; i < nPerson.length; i++) { + if (N1+N2 < N3*k) { // 在i+1层停比较好 + target = i; + N1 += N2 + N3 -= nPerson[i] + N2 = nPerson[i] + } else { + break; + } + } + return target +} +``` + +### 其他优秀解答 +中位数方法 + +假设两个人在2楼和9楼下。那么在2-9楼之间任意层停,两人走楼梯的层数和是不变的 +换一组(第二小、第二大)人也是这么处理 +将每个人要去的楼层从低到高逐一排列,找到中位数,此中位数就是最优楼层 + +时间复杂度O(N) + +```js +/** +* 中位数方法 +*/ +function median(nPerson) { + const newArr = []; // 存楼层 + for(let i=0; i < nPerson.length; i++) { + while(nPerson[i] > 0) { + newArr.push(i) + nPerson[i]-- + } + } + let len = newArr.length; + // 返回楼层中位数 + return len % 2 == 1 ? newArr[(len+1)/2] : newArr[len/2] +} +``` diff --git a/daily/2019-08-02.md b/daily/2019-08-02.md new file mode 100644 index 0000000..7992a79 --- /dev/null +++ b/daily/2019-08-02.md @@ -0,0 +1,78 @@ +# 毎日一题 - 771.jewels-and-stones + +## 信息卡片 + +- 时间:2019-08-02 +- 题目链接:https://leetcode.com/problems/jewels-and-stones/ +- tag:`String` `Hash Table` + +## 题目描述 + +``` + 给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。 + +J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。 + +示例 1: + +输入: J = "aA", S = "aAAbbbb" +输出: 3 +示例 2: + +输入: J = "z", S = "ZZ" +输出: 0 +注意: + +S 和 J 最多含有50个字母。 + J 中的字符不重复。 +``` + +## 参考答案 + +### 正则匹配 + +时间复杂度比较高,具体复杂度取决于内部回溯的时机。 + +思路:正则把石头里的宝石 replace 掉,长度相减,就是结果 + +代码: + +```js +let newS = S; +for (let i = 0; i < J.length; i++) { + newS = newS.replace(new RegExp(J[i], "g"), ""); +} +return S.length - newS.length; +``` + +### Hash Table + +使用 hash table, 空间换时间的方式。 + +代码: + +```js +const set = {}; +let res = 0; + +for (let i = 0; i < J.length; i++) { + set[J[i]] = true; +} + +for (let i = 0; i < S.length; i++) { + if (set[S[i]]) { + res++; + } +} +return res; +``` + +### JS 一行代码 + +```js +return S.split("").filter(c => J.indexOf(c) !== -1).length; +``` + +## 优秀解答 + +> 暂缺 diff --git a/daily/2019-08-05.md b/daily/2019-08-05.md new file mode 100644 index 0000000..b8cd79c --- /dev/null +++ b/daily/2019-08-05.md @@ -0,0 +1,74 @@ +# 毎日一题 - 105.从前序与中序遍历序列构造二叉树 + +## 信息卡片 + +* 时间:2019-08-05 +* 题目链接:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ +- tag:`Tree` `Array` +## 题目描述 +``` +根据一棵树的前序遍历与中序遍历构造二叉树。 + +注意: +你可以假设树中没有重复的元素。 + +例如,给出 + +前序遍历 preorder = [3,9,20,15,7] +中序遍历 inorder = [9,3,15,20,7] +返回如下的二叉树: + + 3 + / \ + 9 20 + / \ + 15 7 +``` +## 参考答案 +递归构造二叉树,时间复杂度O(n) +> +关键在于前序遍历和中序遍历的特性: +* 前序遍历:根节点是首元素 +* 中序遍历:根节点左侧的值是其左子树,右侧的值是其右子树 +> +因此,我们首先要得到从前序序列中获取根节点,然后遍历中序序列,找到根节点的位置,以此直到其左子树和右子树的范围。当我们得到其左子树之后,事情就开始重复了,我们仍然需要根据前序序列中找到这颗左子树的根节点,然后再根据中序序列得到这颗左子树根节点的左右子树,右子树同理。因此实际上就是个回溯。 +```c +struct TreeNode* _buildTree(int* preorder, int* pindex, int* inorder, int inbegin, int inend) +{ + if(inbegin>inend)//区间不存在,空树 + { + return NULL; + } + struct TreeNode* root=(struct TreeNode*)malloc(sizeof(struct TreeNode)); + root->val=preorder[*pindex]; + (*pindex)++; + if(inbegin==inend)//区间只有一个结点,就是根结点 + { + root->val=inorder[inbegin]; + root->left=NULL; + root->right=NULL; + return root; + } + //区间正常 + int rootindex=inbegin; + while(rootindex<=inend)//用前序的根划分中序为两个子区间 + { + if(inorder[rootindex]==root->val) + { + break; + } + else + { + ++rootindex; + } + } + //递归创建左子树 + root->left= _buildTree(preorder, pindex, inorder, inbegin, rootindex-1); + //递归创建右子树 + root->right= _buildTree(preorder, pindex, inorder, rootindex+1, inend); + return root; +} +``` +## 其他优秀解答 + +> 暂缺 \ No newline at end of file diff --git a/daily/2019-08-08.md b/daily/2019-08-08.md new file mode 100644 index 0000000..6f76c61 --- /dev/null +++ b/daily/2019-08-08.md @@ -0,0 +1,85 @@ +# 毎日一题 - 1123.最深叶节点的最近公共祖先 + +## 信息卡片 + +* 时间:2019-08-08 +* 题目链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-deepest-leaves/ +- tag:`DFS` `Tree` +## 题目描述 + +给你一个有根节点的二叉树,找到它最深的叶节点的最近公共祖先。 + +回想一下: + +* 叶节点是二叉树中没有子节点的节点 +* 树的根节点的深度为0,如果某一节点的深度为d,那它的子节点的深度就是d+1 +* 如果我们假定A是一组节点S的最近公共祖先,```S```中的每个节点都在以 A 为根节点的子树中,且 A的深度达到此条件下可能的最大值。 + + +**示例 1:** +``` +输入:root = [1,2,3] +输出:[1,2,3] +``` +**示例 2:** +``` +输入:root = [1,2,3,4] +输出:[4] +``` +**示例 3:** +``` +输入:root = [1,2,3,4,5] +输出:[2,4,5] +``` + +提示: +* 给你的树中将有1 到 1000 个节点。 +* 树中每个节点的值都在 1 到 1000 之间。 +## 参考答案 +深度优先搜索 +> +先来解释一下题目意思,给你一个树根,返回最深叶节点的最近公共祖先,存在以下俩种情况: +* 最深叶节点只有一个,那么这个叶节点本身就是它的最近公共祖先 +* 最深叶节点不止一个,那就不断深搜找到最大深度,然后回溯,出递归栈时最后一个左右子树等高的节点就是该树的最深节点的最近祖先 +> +所以代码思路分俩条路:只有一个最深叶节点找到并更新返回值;存在多个最深叶节点,找到最后一个子节点等高的节点更新返回值。后者的存在可以被证明,所以后者可以更改前者的结果。 +```c++ +class Solution { +private: + TreeNode *ans; + int max_deep; + int DFS(TreeNode *root, int nums){ + //叶子节点 + if(root->left == NULL && root->right == NULL){ + //更新最大深度,记录最大深度的叶节点 + if(nums>max_deep){ + ans = root; + max_deep = nums; + } + return nums; + } + int num_l=0, num_r=0; + //递归左右子树 + if(root->left) num_l = DFS(root->left, nums+1); + if(root->right) num_r = DFS(root->right, nums+1); + //存储多个最深叶节点,递归出最近公共祖先 + if(num_l == num_r && num_l>=max_deep){ + ans = root; + max_deep = num_l; + } + //返回最大深度 + return max(num_l, num_r); + } +public: + TreeNode* lcaDeepestLeaves(TreeNode* root) { + //初始化根、最大深度 + ans = root; + max_deep = INT_MIN; + int deep_n = DFS(root, 1); + return ans; + } +}; +``` +## 其他优秀解答 + +> 暂缺 \ No newline at end of file diff --git a/daily/2019-08-09.md b/daily/2019-08-09.md new file mode 100644 index 0000000..bd899be --- /dev/null +++ b/daily/2019-08-09.md @@ -0,0 +1,69 @@ +# 毎日一题 - 64.最小路径和 + +## 信息卡片 + +* 时间:2019-08-09 +* 题目链接:https://leetcode-cn.com/problems/minimum-path-sum/ +- tag:`动态规划` `Array` +## 题目描述 +给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 +**说明**:每次只能向下或者向右移动一步。 +**示例:** +``` +输入: +[ +  [1,3,1], + [1,5,1], + [4,2,1] +] +输出: 7 +解释: 因为路径 1→3→1→1→1 的总和最小。 +``` +## 参考答案 + +我们新建一个额外的dp数组,与原矩阵大小相同。在这个矩阵中,dp(i,j)表示从原点到坐标(i,j)的最小路径和。我们初始化dp值为对应的原矩阵值,然后去填整个矩阵,对于每个元素考虑从上方移动过来还是从左方移动过来,因此获得最小路径和我们有如下递推公式:`dp(i,j)=grid(i,j)+min(dp(i-1,j),dp(i,j-1))` + + +我们可以使用原地算法,这样就不需要开辟dp数组,空间复杂度可以降低到$O(1)$。 + +```c++ +class Solution { +public: + int minPathSum(vector>& grid) { + int n = grid.size(); + if(n==0) + return 0; + int m = grid[0].size(); + if(m==0) + return 0; + //初始化第一行 + for(int i=1;i 暂缺 diff --git a/daily/2019-08-11.md b/daily/2019-08-11.md new file mode 100644 index 0000000..cfb6036 --- /dev/null +++ b/daily/2019-08-11.md @@ -0,0 +1,103 @@ +# 毎日一题 - 547.朋友圈 + +## 信息卡片 + +* 时间:2019-08-11 +* 题目链接:https://leetcode-cn.com/problems/friend-circles +- tag:`并查集` `BFS` +## 题目描述 +班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。 + +给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。 + +**示例 1:** +``` +输入: +[[1,1,0], + [1,1,0], + [0,0,1]] +输出: 2 +说明:已知学生0和学生1互为朋友,他们在一个朋友圈。第2个学生自己在一个朋友圈。所以返回2。 +``` +**示例 2:** +``` +输入: +[[1,1,0], + [1,1,1], + [0,1,1]] +输出: 1 +说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。 +``` +注意: +1. N 在[1,200]的范围内。 +2. 对于所有学生,有M[i][i] = 1。 +3. 如果有M[i][j] = 1,则有M[j][i] = 1。 + +## 参考答案 +### 解法一:并查集 +遍历邻接矩阵M,如果M[i][j]==1即二者是朋友,那么合并i,j集合,遍历完整个矩阵M后则剩余的集合数量就是有多少个朋友圈。其中路径压缩能大大降低算法的时间复杂度:合并时让当前节点归属指向朋友圈的根节点,下次查询时就能快许多。 +```c++ +class Solution { +public: + int findCircleNum(vector>& M) { + if (M.empty()) + return 0; + vector pre(M.size()); + for(int i=0; i& pre) + { + //“pre[x] = ”这句为路径压缩,直接指向组的根节点,下次查询时就快很多了。 + return pre[x]==x ? x : pre[x] = find(pre[x], pre); + } +}; +``` +### 解法二:BFS +时间复杂度O(n²) +> +可以将题目转换为是在一个图中求连通子图的问题,给出的N*N的矩阵就是邻接矩阵,建立N个节点的visited数组,从not visited的节点开始深度优先遍历,遍历就是在邻接矩阵中去遍历,如果在第i个节点的邻接矩阵那一行中的第j个位置处M[i][j]==1 and not visited[j],就应该dfs到这个第j个节点的位置, + +```java +public class Solution { + public void dfs(int[][] M, int[] visited, int i) { + for (int j = 0; j < M.length; j++) { + if (M[i][j] == 1 && visited[j] == 0) { + visited[j] = 1; + dfs(M, visited, j); + } + } + } + public int findCircleNum(int[][] M) { + int[] visited = new int[M.length]; + int count = 0; + for (int i = 0; i < M.length; i++) { + if (visited[i] == 0) { + dfs(M, visited, i); + count++; + } + } + return count; + } +} +``` \ No newline at end of file diff --git a/daily/2019-08-13.md b/daily/2019-08-13.md new file mode 100644 index 0000000..6ca9f33 --- /dev/null +++ b/daily/2019-08-13.md @@ -0,0 +1,215 @@ +# 毎日一题 - 417. 太平洋大西洋水流问题 + +## 信息卡片 + +* 时间:2019-08-13 +* 题目链接:https://leetcode-cn.com/problems/pacific-atlantic-water-flow +- tag:`Backtracking` `DFS` +## 题目描述 + +给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。 + +规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。 + +请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。 + +提示: + +输出坐标的顺序不重要 +m 和 n 都小于150 + +示例: + +``` +给定下面的 5x5 矩阵: + + 太平洋 ~ ~ ~ ~ ~ + ~ 1 2 2 3 (5) * + ~ 3 2 3 (4) (4) * + ~ 2 4 (5) 3 1 * + ~ (6) (7) 1 4 5 * + ~ (5) 1 1 2 4 * + * * * * * 大西洋 + +返回: + +[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元). +``` + + + +## 参考答案 + +- 方法1:直接采用回溯法 超时 + +直接判断 水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标 +采用方法是 +回溯法(英语:backtracking)是暴力搜索法中的一种。 +在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。 +在这个题目中,这个题目中正好就是如此。 +因为需要等到上下左右全部计算完毕才有确定答案。 + +m 和 n =150,肯定超时。 + +- 方法2:动态规划+回溯法 + +思路: + +总体思路还是回溯,我们对能够流入太平洋的(第一行和第一列)开始进行上下左右探测。 + +同样我们对能够流入大西洋的(最后一行和最后一列)开始进行上下左右探测。 + +最后将探测结果进行合并即可。合并的条件就是当前单元既能流入太平洋又能流入大西洋。 + +![集合](https://user-images.githubusercontent.com/5937331/63209454-7c921a80-c113-11e9-8d74-82d0476b8828.png) +扩展: + +如果题目改为能够流入大西洋或者太平洋,我们只需要最后合并的时候,条件改为求或即可 + +## 参考代码 + +- JavaScript Code + +```js +function dfs(i, j, height, m, matrix, rows, cols) { + if (i >= rows || i < 0) return; + if (j >= cols || j < 0) return; + + if (matrix[i][j] < height) return; + + if (m[i][j] === true) return; + + m[i][j] = true; + + dfs(i + 1, j, matrix[i][j], m, matrix, rows, cols); + dfs(i - 1, j, matrix[i][j], m, matrix, rows, cols); + dfs(i, j + 1, matrix[i][j], m, matrix, rows, cols); + dfs(i, j - 1, matrix[i][j], m, matrix, rows, cols); +} +/** + * @param {number[][]} matrix + * @return {number[][]} + */ +var pacificAtlantic = function(matrix) { + const rows = matrix.length; + if (rows === 0) return []; + const cols = matrix[0].length; + const pacific = Array.from({ + length: rows + }, + () = >Array(cols).fill(false)); + const atlantic = Array.from({ + length: rows + }, + () = >Array(cols).fill(false)); + const res = []; + + for (let i = 0; i < rows; i++) { + dfs(i, 0, 0, pacific, matrix, rows, cols); + dfs(i, cols - 1, 0, atlantic, matrix, rows, cols); + } + + for (let i = 0; i < cols; i++) { + dfs(0, i, 0, pacific, matrix, rows, cols); + dfs(rows - 1, i, 0, atlantic, matrix, rows, cols); + } + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (pacific[i][j] === true && atlantic[i][j] === true) res.push([i, j]); + } + } + + return res; +}; +``` + + +- C++ Code + + ```c++ + class Solution { + public: + vector > pacificAtlantic( vector > & matrix ) + { + vector > out; + int row = matrix.size(); + if ( 0 == row ) + return(out); + int col = matrix[0].size(); + if ( 0 == col ) + return(out); + + /* 能流动到“太平洋"的陆地 */ + vector > dp1( row, vector( col, false ) ); + /* 能流动到“大西洋"的陆地 */ + vector > dp2( row, vector( col, false ) ); + + /* 从第一行/最后一行出发寻找连同节点,不变的x坐标 */ + for ( int j = 0; j < col; j++ ) + { + dfs( 0, j, INT_MIN, matrix, dp1 ); + dfs( row - 1, j, INT_MIN, matrix, dp2 ); + } + /* 从第一列/最后一列出发寻找连同节点,不变的y坐标 */ + for ( int i = 0; i < row; i++ ) + { + dfs( i, 0, INT_MIN, matrix, dp1 ); + dfs( i, col - 1, INT_MIN, matrix, dp2 ); + } + + vector temp( 2 ); + for ( int i = 0; i < row; i++ ) + { + for ( int j = 0; j < col; j++ ) + { + /* 请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。 */ + if ( dp1[i][j] == true && dp2[i][j] == true ) + { + temp[0] = i; + temp[1] = j; + out.push_back( temp ); + } + } + } + return(out); + } + + + void dfs( int row, int col, int height, + vector > & matrix, vector > & visited ) + { + if ( row < 0 || row >= matrix.size() || + col < 0 || col >= matrix[0].size() + ) + { + return; + } + + if ( visited[row][col] == true ) + { + return; + } + + if ( height > matrix[row][col] ) + { + return; + } + + visited[row][col] = true; + + dfs( row + 1, col, matrix[row][col], matrix, visited ); + dfs( row - 1, col, matrix[row][col], matrix, visited ); + dfs( row, col + 1, matrix[row][col], matrix, visited ); + dfs( row, col - 1, matrix[row][col], matrix, visited ); + } + }; + ``` + + + + + +## 其他优秀解答 + +> ##### 暂缺 diff --git a/daily/2019-08-16.md b/daily/2019-08-16.md new file mode 100644 index 0000000..585525b --- /dev/null +++ b/daily/2019-08-16.md @@ -0,0 +1,51 @@ +# 每日一题 - 字符串首尾相等的最长子串 + +## 信息卡片 + +* 时间:2019-08-16 +* 题目链接:无 +* tag:`String` `Hash Table` +## 题目描述 +``` +求一个字符串首尾相等的最长子串,例如abcba,最长就是abcba +``` +## 参考答案 +思路: + +头尾两个字母相同的子串,比如abca,这里头尾相同的就是a..a子串,它的长度就是这两个a的下标之差+1; + +如果是abcadefa,这里有三个a,那么这个子串的长度是第一个a和最后一个a的下标之差+1; + +这时候规律就来了,那我只要计算同一个字母第一次出现和最后一次出现的位置就好了嘛,最后再求个最大值; + +那这样的话,我们只要一次遍历,用一个map把这些位置记下来即可。 + +但是仔细想想,我存同一个字母的这么多位置,好像最后我也只取这个位置集合的第一个和最后一个啊,那我为什么还要存这么多,存起始位置就好了嘛! +每次遍历到第2,3,4个相同字母的时候,我都减去第一个此字母位置的下标再看看这个差值是不是最大的。 +所以代码来了: + +JavaScript Code: +```js +var LES = function (str) { + var map = {}; // 用来存储遍历到的字母出现的第一个位置 + var maxLen = 1; // 初始化最大子串长度 + var substrStartIndex = 0; // 最长子串的起始位置 + for (var i = 0; i < str.length; i++) { + var char = str[i]; + if (map[char] != null) { // 如果这个字母之前已经出现过了 + if (i - map[char] + 1 > maxLen) { // 那么计算当前这个字母到第一次出现的位置距离,然后比较 + maxLen = i - map[char] + 1; + substrStartIndex = map[char]; // 如果是最大值,记录下当前最大子串的起始位置 + } + } else { + map[char] = i;// 如果这个字母之前没出现过,那么记下它的下标 + } + } + + return str.slice(substrStartIndex, substrStartIndex + maxLen); +} +``` + +## 优秀解答 + +>暂缺 diff --git a/daily/2019-08-19.md b/daily/2019-08-19.md new file mode 100644 index 0000000..5f3ebd3 --- /dev/null +++ b/daily/2019-08-19.md @@ -0,0 +1,100 @@ +## 每日一题 - 593. 有效的正方形 + +### 信息卡片 + +- 时间:2019-06-08 +- 题目链接:https://leetcode.com/problems/top-k-frequent-elements/description/ +- tag:`Hash Table` `Heap` + +### 题目描述 + +``` +Given a non-empty array of integers, return the k most frequent elements. + +Example 1: + +Input: nums = [1,1,1,2,2,3], k = 2 +Output: [1,2] + +Example 2: + + +Input: nums = [1], k = 1 +Output: [1] + +Note: + +You may assume k is always valid, 1 ≤ k ≤ number of unique elements. +Your algorithm's time complexity must be better than O(n log n), where n is +the array's size. +``` + + + +### 参考答案 + +模仿 [@raof01](https://github.com/raof01) 的思路写的JS代码, + +基本思路就是: 证明四个角都是直角, 而证明直角的方式就是边长关系。 + +四个点一共有六个连接的线段,其中两个是对角线,另外四个是边。 + +对于直角来说,满足“a * a + b * b = c * c”, 由于是正方形,所以a = b, 因此c就等于 +2 * a * a , 其中a为边长,c就是对角线的长度。 + + +我们分别计算出距离的平方,如果有四个相同,另外两个相同。 且二者的关系可以满足直角,那么他就有四个直角,他就是一个正方形 + +```js +/* + * @lc app=leetcode id=593 lang=javascript + * + * [593] Valid Square + */ +function square(p1, p2) { + const deltaX = p1[0] - p2[0]; + const deltaY = p1[1] - p2[1]; + + return deltaX * deltaX + deltaY * deltaY; +} +/** + * @param {number[]} p1 + * @param {number[]} p2 + * @param {number[]} p3 + * @param {number[]} p4 + * @return {boolean} + */ +var validSquare = function (p1, p2, p3, p4) { + // 证明四个角都是直角 + // 证明直角的方式就是边长关系 + const squares = [ + square(p1, p2), + square(p1, p3), + square(p1, p4), + square(p2, p3), + square(p2, p4), + square(p3, p4) + ]; + let cnt1 = 0; + let cnt2 = 0; + let sum = 0; + + for(let i = 0; i < squares.length; i++) { + sum += squares[i]; + } + + for(let i = 0; i < squares.length; i++) { + if (sum === 8 * squares[i]) { + cnt1++; + } else if(sum === 4 * squares[i]) { + cnt2++; + } + } + + return cnt1 === 4 && cnt2 ===2; + +} +``` +### 其他优秀解答 + +暂无 diff --git a/daily/2019-08-21.md b/daily/2019-08-21.md new file mode 100644 index 0000000..9ebfd30 --- /dev/null +++ b/daily/2019-08-21.md @@ -0,0 +1,66 @@ +# 毎日一题 - 桶中取黑白球 + +## 信息卡片 + +* 时间:2019-08-21 +* tag:`Math` `位运算` +## 题目描述 +``` +有一个桶,里面有白球,黑球各100个,你必须用以下规则将球取出来: +- 每次从桶里取两个球 +- 如果两个球是相同的颜色,那么再放一个黑球 +- 如果两个球是不同的颜色,那么再放一个白球。 +问:最后一个球是黑球的概率是多少? +``` + +## 参考答案 + +### 1. 数学分析原问题 + +首先我们来仔细读题看看我们有哪些知道的信息: + +- 不管什么情况,每次球的总数减1; +- 两黑:黑球-1,白球0; +- 两白:黑球+1,白球-2; +- 一黑一白:黑球-1,白球0; +- 最后两球只要不是一黑一白,最后一球都是黑; + +初始状态是100个黑球和100个白球,从上面三个状态可知道,黑球要么+1要么-1,而白球要么不变要么-2;在198次取球后,我们可知剩余两个球,现在假设剩余的两球为一黑一白,可以证明这是不存在的。 + +因为白球下降是以2的倍数下降,不可能从100下降至1,;故剩余两球肯定不是一黑一白的情况,那么最后一球的情况必然为黑。 + + +### 2. 原问题拓展(n个黑球和m个白球) + +在n+m-2次取球后,剩余两个球。 + +由于我们知道白球数下降是以2的倍数下降,如果m为偶数的话,是不可能下降至1;即同上1,最后一球必为黑球。如果m为奇数的话,最后必然是k黑1白(k>=1),显然对于任意的k,要么剩余全是黑球,要么黑球不断减1,最后变为1黑1白。全黑和1黑1白最后的结果都是剩余一个白球。 + +得出结论,最后一球结果无关黑球数量(n>=0),仅与白球数量m有关。 + +- 如果白球m为奇数,最后一球必然白; +- 如果白球m为偶数,最后一球必然黑; + +### 3. 抽象为数学模型,严格证明 + +不妨设黑球为0,白球为1; + +- 两黑:F(0,0) = 0;表示两个黑球生一黑; +- 两白:F(1,1) = 0;表示两个白球生一黑; +- 一黑一白:F(0,1) = 0;表示一个黑球一个白球生一白; + +仔细观察就会发现这个函数F就是XOR(异或); + +那么m个黑球和n个白球,就抽象为m个0和n个1作异或的结果;而且我们可知异或满足结合律和交换律(证明略,最简单的证明方法枚举)。 + +那么问题就很简单,对于任意多0,异或结果依然是0,所以对于任意多1,只需要考虑1个数的奇偶性就可判断最后剩余1个1还是0个1; + +结论同2: + +- 1(白球)的个数奇数,最后异或结果为1; +- 1(白球)的个数偶数,最后异或结果为0; + + +## 优秀解答 + +>暂缺 diff --git a/daily/2019-09-15.md b/daily/2019-09-15.md new file mode 100644 index 0000000..8707ea8 --- /dev/null +++ b/daily/2019-09-15.md @@ -0,0 +1,162 @@ +# 毎日一题 - 水壶问题 + +## 信息卡片 + +* 时间:2019-09-15 +* 题目链接:https://leetcode-cn.com/problems/water-and-jug-problem/ +* tag:`Math` +## 题目描述 +``` +给你一个装满水的 8 升满壶和两个分别是 5 升、3 升的空壶,请想个优雅的办法,使得其中一个水壶恰好装 4 升水,每一步的操作只能是倒空或倒满。 +``` + +## 参考答案 + +1.数学分析解答 + +上面的问题是一个特例,我们可以抽象为[leetcode-365-水壶问题](https://leetcode-cn.com/problems/water-and-jug-problem/)。 +``` +有两个容量分别为 x升 和 y升 的水壶(壶1,壶2)以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水? +``` + +解题核心思路(x < y,即壶1容量小于壶2,x == y的情况后面讨论): + +1. 将壶2倒满,往壶1倒入至满。 +2. 若壶1满,记录当前壶2中新水量。壶1倒出,将壶2中剩余的继续往壶1中倒入;(当壶1满,继续此操作,并记录当前壶2中新水量nw, 若此新水量已被记录,则)。 +3. 若出现壶1不满时(即此时壶2必空),重复操作1。 + +开辟一个新数组nws记录所有新水量,对任意nws[i],可构造的水量为nws[i],nws[i]+x,nws[i]+y。 + +(其实不需要新数组,因为数学上可以证明新水量的值循环周期呈现,故可以使用一个临时变量cur,当cur==x为终止条件) + +数学证明新水量nw值是循环周期的: +![1111]() + +个别特殊情况考虑: + +- x == z || y == z; **true** +- x == 0 || x+y < z; **false** +- x+y == z || z == 0; **true** + +```c++ +class Solution { +public: + bool canMeasureWater(int x, int y, int z) { + if(x > y) return canMeasureWater(y, x, z); + if(x == z || y == z) return true; + if(x == 0 || x+y < z) return false; + if(x+y == z || z == 0) return true; + int cur = y - x; + while(cur != x){ + if(cur == z) return true; + if(cur > x){ + if(cur + x == z) return true; + cur = cur - x; + } + else{ + if(cur + y == z || cur + x == z) return true; + cur = y - x + cur; + } + } + return false; + } +}; +``` + +2.BFS + +不仅可以计算是否能获取 z 升水,而且可以获得最少多少操作可获取 z 升水。(缺点,无法通过,因为需要太大的空间,需要申请一个三维数组记录状态) + +核心思想就是状态转移问题: + +壶0(x+y),壶1(x),壶2(y),壶0是本是无限大水池,同理于定义为大小为x+y的壶。用bfs的思想,使用一个队列记录所有新的状态。 + +对于任意状态(c,a,b),状态转移就是: + +- 若c不为0,将壶0倒水入壶1或壶2;若a不为0,将壶1倒水入壶0或壶2;若b不为0,将壶2倒水入壶0或壶1; +- 记录每个新状态,并入队,若此状态访问过则不入队。 + +特殊情况考虑同1。 + +```c++ +class Solution { +public: + struct state{ + int nums[3]; + state(int xy, int x, int y){ + nums[0] = xy; + nums[1] = x; + nums[2] = y; + } + }; + + state pour_water(state cur, int src, int det, int size[]){ + state ans = cur; + int need_w = size[det] - cur.nums[det]; + if(need_w <= cur.nums[src]){ + ans.nums[det] += need_w; + ans.nums[src] -= need_w; + } + else{ + ans.nums[det] += ans.nums[src]; + ans.nums[src] = 0; + } + return ans; + } + + bool canMeasureWater(int x, int y, int z) { + if(x > y) return canMeasureWater(y, x, z); // + if(x == z || y == z) return true; + if(x == 0 || x+y < z) return false; + if(x+y == z || z == 0) return true; + int visited[x+y+1][x+1][y+1]; + int water_size[3] = {x+y, x, y}; + memset(visited, 0, sizeof(visited)); + state cur(x+y, 0, 0); + queue q; + q.push(cur); + int step = 0; + while(!q.empty()){ + int size = q.size(); + while(size){ + state temp(0, 0, 0); + cur = q.front(); + if(cur.nums[1] + cur.nums[2] == z) return true; + visited[cur.nums[0]][cur.nums[1]][cur.nums[2]] = 1; + q.pop(); + if(cur.nums[0] != 0){ + temp = pour_water(cur, 0, 1, water_size); + if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1) + q.push(temp); + temp = pour_water(cur, 0, 2, water_size); + if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1) + q.push(temp); + } + if(cur.nums[1] != 0){ + temp = pour_water(cur, 1, 2, water_size); + if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1) + q.push(temp); + temp = pour_water(cur, 1, 0, water_size); + if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1) + q.push(temp); + } + if(cur.nums[2] != 0){ + temp = pour_water(cur, 2, 1, water_size); + if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1) + q.push(temp); + temp = pour_water(cur, 2, 0, water_size); + if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1) + q.push(temp); + } + size--; + } + step++; + } + return false; + } +}; +``` + +## 优秀解答 + +>暂缺 diff --git a/daily/2019-09-16.md b/daily/2019-09-16.md new file mode 100644 index 0000000..7b8c927 --- /dev/null +++ b/daily/2019-09-16.md @@ -0,0 +1,158 @@ +# 毎日一题 - 版本号比较 + +## 信息卡片 + +* 时间:2019-09-16 +* 题目链接: +* tag:`String` +## 题目描述 +``` +比较两个版本号 version1 和 version2。 +如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。 + +你可以假设版本字符串非空,并且只包含数字和 . 字符。 + + . 字符不代表小数点,而是用于分隔数字序列。 + +例如,2.5 不是“两个半”,也不是“差一半到三”,而是第二版中的第五个小版本。 + +你可以假设版本号的每一级的默认修订版号为 0。例如,版本号 3.4 的第一级(大版本)和第二级(小版本)修订号分别为 3 和 4。其第三级和第四级修订号均为 0。 + +示例 1: + +输入: version1 = "0.1", version2 = "1.1" +输出: -1 +示例 2: + +输入: version1 = "1.0.1", version2 = "1" +输出: 1 +示例 3: + +输入: version1 = "7.5.2.4", version2 = "7.5.3" +输出: -1 +示例 4: + +输入:version1 = "1.01", version2 = "1.001" +输出:0 +解释:忽略前导零,“01” 和 “001” 表示相同的数字 “1”。 +示例 5: + +输入:version1 = "1.0", version2 = "1.0.0" +输出:0 +解释:version1 没有第三级修订号,这意味着它的第三级修订号默认为 “0”。 + +提示: + +版本字符串由以点 (.) 分隔的数字字符串组成。这个数字字符串可能有前导零。 +版本字符串不以点开始或结束,并且其中不会有两个连续的点。 +``` + +## 参考答案 + +### 1. 递归解决 + +其实这个问题其实简化后就是依次比较每一个修订版本大小,所以问题有以下几点: + +1. 获取每个修订版本号大小; +2. 处理每个修订版本号前导零问题; +3. 处理不同版本有不同次数修订版本; + +问题1:这个如果对字符串处理比较熟悉的会比较简单,直接遍历循环找到第一个逗号first_dot(找不到的情况设为-1),str.substr(0, first_dot)即可。针对第二,第三个逗号,我们用递归的方案回避,这样每次我们都相当于找第一个逗号前的数字。 + +问题2:前导零问题更容易解决,在遍历过程中找到第一个非零数first_no_zero,str.substr(first_no_zero, first_dot - first_no_zero)。当然更简单的方案是定义初值v1 = 0,每次计算v1 = v1*10 + str[i] - 'a' + +问题3:针对不同次数的修订版本,我们可以在字符串末尾填0表示。即有一个版本号first_dot = -1。 + +代码如下: + +```c++ +class Solution { +public: + int first_num(string str, int& first_dot){ + int v1 = 0; + first_dot = -1; + for(int i = 0; i < str.size(); i++){ + if(str[i] == '.'){ + first_dot = i; + break; + } + else + v1 = v1 * 10 + (str[i] - '0'); + } + return v1; + } + + int compareVersion(string version1, string version2) { + int v1 = 0, v2 = 0; + int v1_first_dot, v2_first_dot; + v1 = first_num(version1, v1_first_dot); + v2 = first_num(version2, v2_first_dot); + if(v1 > v2) + return 1; + else if(v1 < v2) + return -1; + else{ + if(v1_first_dot == -1 && v2_first_dot == -1) + return 0; + if(v1_first_dot == -1) + version1 = "0"; + else + version1 = version1.substr(v1_first_dot+1); + if(v2_first_dot == -1) + version2 = "0"; + else + version2 = version2.substr(v2_first_dot+1); + return compareVersion(version1, version2); + } + } +}; + +``` + +### 2. 数组 + +解析每个版本号,放入数组,依次比较大小。 + +```c +int compareVersion(char * version1, char * version2){ + if (version1 == NULL || version2 == NULL) return -1; + int *val1 = (int *)calloc(1024, sizeof(int)); + int *val2 = (int *)calloc(1024, sizeof(int)); + int len1 = strlen(version1), top1 = 0; + int len2 = strlen(version2), top2 = 0; + int i, n; + for (i = 0, n = 0; i < len1; ++i) { //解析版本1 + if (version1[i] == '.') { + val1[top1++] = n; + n = 0; + }else n = n*10 + (version1[i] & 0x0f); + } + val1[top1++] = n; + for (i = 0, n = 0; i < len2; ++i) { //解析版本1 + if (version2[i] == '.') { + val2[top2++] = n; + n = 0; + }else n = n*10 + (version2[i] & 0x0f); + } + val2[top2++] = n; + for (i = 0; i < top1 && i < top2; ++i) { //比较版本大小 + if (val1[i] > val2[i]) return 1; + else if (val1[i] < val2[i]) return -1; + } + if (i < top1) { //由于可能有的版本还没遍历完 + while (i < top1) if (val1[i++]) return 1; //只要版本后面的数字出现的不是0,就意味着两个版本不一样 + }else{ + while (i < top2) if (val2[i++]) return -1; + } + return 0; +} +//作者:ljj666 +//链接:https://leetcode-cn.com/problems/compare-version-numbers/solution/cyu-yan-jian-jian-dan-dan-de-ji-xing-dai-ma-jie-37/ +//来源:力扣(LeetCode) +//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 +``` + + +## 优秀解答 + +>暂缺 diff --git a/daily/2019-09-23.md b/daily/2019-09-23.md new file mode 100644 index 0000000..b6d7fa4 --- /dev/null +++ b/daily/2019-09-23.md @@ -0,0 +1,166 @@ +# 毎日一题 - 反转每对括号间的子串 + +## 信息卡片 + +* 时间:2019-09-23 +* 题目链接:https://leetcode-cn.com/problems/reverse-substrings-between-each-pair-of-parentheses +* tag:`String` `Backtracking` +## 题目描述 +``` +给出一个字符串 s(仅含有小写英文字母和括号)。 + +请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。 + +注意,您的结果中 不应 包含任何括号。 + + + +示例 1: + +输入:s = "(abcd)" +输出:"dcba" +示例 2: + +输入:s = "(u(love)i)" +输出:"iloveu" +示例 3: + +输入:s = "(ed(et(oc))el)" +输出:"leetcode" +示例 4: + +输入:s = "a(bcdefghijkl(mno)p)q" +输出:"apmnolkjihgfedcbq" + + +提示: + +0 <= s.length <= 2000 +s 中只有小写英文字母和括号 +我们确保所有括号都是成对出现的 +``` + +## 参考答案 + +#### 思路 + +1. 对字符串中的字符遍历 + + +2. 括号是有层次性的,所以这里用递归的方式处理内层的字符串,内层处理完成后回溯。返回内层有括号索引的下一个位置,以及括号内反转完成后的字符串。 + + +3. 在2的递归过程中,原字符串也需要作为递归方法的参数传递进来 + + +4. 递归方法的负责处理从当前位置开始到遇到对应的有括号之间的字符串,将其反转;若遇到新的左括号,创建临时的StringBuilder用于记录递归方法内反转的字符串,进入新的一层递归;递归完成后临时StringBuilder被填充并且是反转后的结果,append到上一层递归的StringBuilder中。 + + +5. 递归回溯到最上层时,StringBuilder即为最终结果。 + + +6. 更多代码细节可以关注下代码注释 + +#### 代码如下 + +```java +package com.jinyang.algorithms.string; + +/** + * Created by Zhang.Jinyang&Hardy on 2019/10/17. + */ +public class ReverseParentheses { + + public static void main(String[] args) { + + /** + * 用例的类型 + * (ab(cd)ef) + * ab(cd) + * ab(cd)ef + * ((ab)c)def + * abc(d(ef)) + * */ + String s = "ab((cd)ef)"; + + StringBuilder builder = new StringBuilder(); + reverseParentheses(s, 0, builder, 0); + System.out.println(builder.toString()); + } + + /** + * leetcode 1190 + * 耗时 1ms + * 内存消耗 34.6M + * */ + static int reverseParentheses(String s, int index, StringBuilder stringBuilder, int leftCount) { + + + //遍历字符串 + while (index < s.length()) { + /** + * case 当前字符为'(' + * leftCount++; 记录已遍历 但未 找到相对应的右括号 的左括号数目;这里每当当前字符为'(',leftCount+1 + * case: index 为 0,第一个字符为'(' + * index ++, 继续遍历 + * case:index 不为0, 不是第一个字符 + * 递归,index++,new stringBuilder(临时存放从当前'('到其相对应的')'里的字符,不包含'('和')'), leftCount + * 递归后,new stringBuilder已被填充好 从当前'('到其相对应的')'里的字符 反转后的字符串;且返回参数为下一次要访问的字符下标index + * leftCount --;因为进入递归后返回时已经将 当前'(' 与其响应的 ')' 内的字符反转,所以左括号数减1 + * 递归前的 stringBuilder append 递归后,已反转的 new stringBuilder + * 继续遍历. + * */ + if (s.charAt(index) == '(') { + leftCount++; + if (index != 0) { + StringBuilder sTemp = new StringBuilder(); + index = reverseParentheses(s, index + 1, sTemp, leftCount); + leftCount--; + stringBuilder.append(sTemp); + continue; + } + + index++; + continue; + } + + /** + * case 当前字符为')' + * 反转stringBuilder里的字符位置; + * index++; + * case:当前leftCount >1 那么当前是在递归过程中 + * return index;//回溯 + * case: 当前leftCount <=1 当前不是递归 + * 继续遍历; + * */ + if (s.charAt(index) == ')') { + stringBuilder.reverse(); + index++; + if (leftCount > 1) { + return index; + } else { + continue; + } + } + + /**当前字符不是'(' 或 ')' + * 当前stringBuilder append 当前下标对应的字符 + * index++ + * 继续遍历 + * */ + stringBuilder.append(s.charAt(index)); + index++; + } + return index; + } + + +} + + +``` + + +## 优秀解答 + +>暂缺 diff --git a/daily/2019-10-11.md b/daily/2019-10-11.md new file mode 100644 index 0000000..3e31ab8 --- /dev/null +++ b/daily/2019-10-11.md @@ -0,0 +1,99 @@ +# 毎日一题 - 拼凑硬币 + +## 信息卡片 + +* 时间:2019-10-11 +* 题目链接:腾讯真题 +* tag:`Bit` `DP` +## 题目描述 +``` +小Q十分富有,拥有非常多的硬币,小Q拥有的硬币是有规律的,对于所有的非负整数K,小Q恰好各有两个面值为2^k的硬币,所有小Q拥有的硬币就是1,1,2,2,4,4,8,8.....小Q有一天去商店购买东西需要支付n元钱,小Q想知道有多少种方案从他拥有的硬币中选取一些拼凑起来恰好是n元(如果两种方案某个面值的硬币选取的个数不一样就考虑为不一样的方案) +``` + +## 参考答案 + +### 1. 二进制方案 + +#### 分析 + +集合:和为n的可选数据集,在本题中就是可选的硬币面值:1,1,2,2,4… 以下统一称为**集合** + +1. 由集合中元素的特点可以联想到 **二进制**。如果将集合中的所有元素都用二进制来表示的话: + + 1 等于 2的0次方 等于 二进制的 1 + 2 等于 2的1次方 等于 二进制的 10 + 4 等于 2的2次方 等于 二进制的 100 + ... + 即: + 1=2^0=(1)2; 2=2^1=(10)2; 4 = 2^2 = (100)2;..... .**(等号最后的数都是二进制表示法)** + + 集合中的每一个元素都可以表示为 首位为 1 其他位为 0 的二进制数。 + +2. 集合中元素是成对出现的,那么可以将集合拆分为完全相同的两部分, 每一个数都可以由二进制中指定位置的1 来表示: + + 8 4 2 1 —> 1 1 1 1 + + 8 4 2 1 —> 1 1 1 1 + + 那么,若目标数 n 为 11.那么 11 = 1 + 10 = 2 + 9 = 3 + 8 = 4 + 7 = 5 + 6; + + **a.** 以 **1 + 10** 为例 : 1 的 二进制 1, 10的二进制为 1010 那么 (11) 就相当于 + + 取 二进制数 第一位的1, 第四位的1 ,第2位的 1 [**从右往左**] + + 即可组成 十进制的11。 + + **b.** **2 + 9 = (10)2+ (1001)2** : 取第二位的1, 第四位的1, 从第一位的1 [**从右往左**] + + 同理: + + **c.** **3 + 8= (11)2+(1000)2** + + **d.** **4 + 7 = (100)2+(111)2** + + **e.** **5 + 6=(101)2+(110)2** + + > a 和 b ,c 中 其实是同一种方案。可通过**异或运算**进行去重: + > + > 比如,这三组方案的异或结果是相同的。1^10 == 2^9 == 3^8 == (1011)2 + > + > d,e 也是同一种方案 + +#### 思路 + +1. 从i=0开始**(如果n=4 那么 0+4 也是一种方案,只不过只选择一个 4 而已)**,i<=(n/2), 每次i+1 循环开始:循环中将 i^(n-i)的值放到 Set(无重复元素)中 +2. **最终**求 Set的 size大小。就是最终的方案数。 + +代码如下: + +```c++ +import java.util.HashSet; +import java.util.Set; + +public class CoinsOfQy { + + /**测试*/ + public static void main(String[] args) { + + int n = 11; + int result = coinsOfQy(n); + System.out.println(result); + } + + private static int coinsOfQy(int n) { + + Set resultSet = new HashSet<>(); + for (int i =0; i<= n/2; i++){ + int r = i^(n-i); + resultSet.add(r); + } + return resultSet.size(); + } +} + +``` + + +## 优秀解答 + +>暂缺 diff --git a/daily/README.md b/daily/README.md new file mode 100644 index 0000000..966f310 --- /dev/null +++ b/daily/README.md @@ -0,0 +1,283 @@ +## 每日一题 + +每日一题是在交流群(包括微信和 qq)里进行的一种活动,大家一起 +解一道题,这样讨论问题更加集中,会得到更多的反馈。而且 +这些题目可以被记录下来,日后会进行筛选添加到仓库的题解模块 + +# 综合认领区 +这里你可以看到所有的每日一题的状态信息。地址: https://github.com/azl397985856/fe-interview/projects/1 + +如果想要认领“未认领”的题目,请参考下方的认领步骤。 +## 认领步骤 + +1. 首先到领取区查看有哪些待领取的,传送门https://github.com/azl397985856/leetcode/projects/1 +2. 选择一个你感兴趣的 +3. 然后到对应issue下留言“认领” +4. 这个时候我会把你领取的从”待领取”移动到”进行中”,并把你分配为解决者 +5. 开始整理,关于格式可以参考:https://github.com/azl397985856/leetcode/pull/24/files +6. 整理完成你可以提一个pr +7. 我合并之后会将其从”进行中”移动到”已合并” +8. 你会成为项目的”贡献者” + +> 题目描述以及优秀答案可以从issue的讨论中收集 + +### 历史汇总 + +#### [14.Longest-Common-Prefix](./2019-06-03.md) + +tag: `trie` `binary search` + +时间:2019-06-03 + +#### [134.Gas Station](./2019-06-04.md) + +tag: `Array` + +时间: 2019-06-04 + +#### [448.Find All Numbers Disappeared in an Array](./2019-06-05.md) + +tag: `Array` + +时间: 2019-06-05 + +#### [739.Daily Temperatures](./2019-06-06.md) + +tag: `Array` `Stack` + +时间: 2019-06-06 + +#### [347.Top K Frequent Elements](./2019-06-08.md) + +tag: `Hash Table` `Heap` + +时间: 2019-06-08 + +#### [10.Regular Expression Matching](./2019-06-09.md) + +tag: `String` `Dynamic Programming` `Backtracking` + +时间: 2019-06-09 + +#### [617. Merge Two Binary Trees](./2019-06-10.md) + +tag: `Tree` + +时间: 2019-06-10 + +### [重复数据排序优化](./2019-06-11.md) + +tag: `Sort` `Quick Sort` + +时间: 2019-06-11 + +### [三门问题](./2019-06-13.md) + +tag: `Probability Theory` + +时间: 2019-06-13 + +### [114.flatten-binary-tree-to-linked-list](./2019-06-14.md) + +tag: `tree` + +时间: 2019-06-14 + +### [744.find-smallest-letter-greater-than-target](./2019-06-17.md) + +tag:`Array` `binary search` + +时间:2019-06-17 + +### [letter-combinations-of-a-phone-number](./2019-06-18.md) + +tag: `backtrack` + +时间: 2019-06-18 + +### [big-countries](./2019-06-19.md) + +tag: `sql` + +时间: 2019-06-19 + +### [594.longest-harmonious-subsequence](./2019-06-20.md) + +tag:`Array` + +时间:2019-06-20 + +### [nth-highest-salary](./2019-06-21.md) + +tag: `sql` + +时间: 2019-06-21 + +### [poker-reveal](./2019-06-26.md) + +tag: `stack` `queue` `backtrack` + +时间: 2019-06-26 + +### [my-sqrt](./2019-06-27.md) + +tag: `binary search` `math` + +时间: 2019-06-27 + +### [deliver-medicine](./2019-07-01.md) + +tag: `logic` + +时间: 2019-07-01 + +### [longest-univalue-path](./2019-07-04.md) + +tag:`recursive` `tree` + +时间: 2019-07-04 + +### [赛马问题](./2019-07-08.md) + +tag:`dAc` + +时间: 2019-07-08 + +### [称球问题](./2019-07-10.md) + +tag:`math` + +时间: 2019-07-10 + +### [圆桌一先一后](./2019-07-15.md) + +tag: `逻辑思维` + +时间:2019-07-15 + +### [squares-of-a-sorted-array](./2019-07-18.md) + +tag::`Array` `Two Pointers` + +时间: 2019-07-18 + +### [洗牌算法](./2019-07-19.md) + +tag::`Array` `Probability` + +时间: 2019-07-19 + +### [524.longest-word-in-dictionary-through-deleting](./2019-07-22.md) + +tag:`String` `Two Pointers` + +时间: 2019-07-22 + +### [删除没有头节点的单链表中的指定项](./2019-07-23.md) + +tag:`Linked List` + +时间: 2019-07-23 + +### [灯泡问题](./2019-07-24.md) + +tag:`发散思维` + +时间: 2019-07-24 + +### [9.palindrome-number](./2019-07-25.md) + +tag:`Math` + +时间: 2019-07-25 + +### [将帅问题](./2019-07-26.md) + +tag:`数据压缩` + +时间: 2019-07-26 + +### [54.Spiral Matrix](./2019-07-29.md) + +tag:`Array` `Matrix` + +时间: 2019-07-29 + +### [走地球问题](./2019-07-30.md) + +tag: `几何` + +时间:2019-07-30 + +### [电梯调度问题](./2019-07-31.md) + +tag: `Math` `Dynamic Programming` + +时间:2019-07-31 + +### [105.从前序与中序遍历序列构造二叉树](./2019-08-05.md) + +tag: `Tree` `Array` + +时间:2019-08-05 + +### [1123.最深叶节点的最近公共祖先](./2019-08-08.md) + +tag: `Tree` `Array` + +时间:2019-08-08 +### [字符串首尾相等的最长子串](./2019-08-16.md) + +tag: `String` `Hash Table` + +时间:2019-08-16 + +### [64.最小路径和](./2019-08-09.md) + +tag: `动态规划` `Array` + +时间:2019-08-09 + +### [547.朋友圈](./2019-08-11.md) + +tag: `并查集` `BFS` + +时间:2019-08-11 + + +### [771.jewels-and-stones](./2019-08-02.md) + +tag:`String` `Hash Table` + +时间: 2019-08-02 + +### [417. 太平洋大西洋水流问题](./2019-08-13.md) + +tag: `Backtracking` `DFS` + +时间: 2019-08-13 + + +### [593. 有效的正方形和](./2019-08-19.md) + +tag: `Array` `Math` + +时间:2019-08-19 + +### [子数组的最大乘积](https://mp.weixin.qq.com/s/nb8zwPathjO9p4GCmmGTmQ) + +tag: `Array` `Math` + +时间: 2019-08-22 + +### [拼凑硬币](./2019-10-11.md) + +tag: `DP` `Bit` + +时间: 2019-10-11 + +### [1190.反转每对括号间的子串](./2019-09-23.md) + +tag: `String` `Backtracking` + +时间: 2019-09-23 diff --git a/daily/answers/114.flatten-binary-tree-to-linked-list.js b/daily/answers/114.flatten-binary-tree-to-linked-list.js new file mode 100644 index 0000000..ec572fb --- /dev/null +++ b/daily/answers/114.flatten-binary-tree-to-linked-list.js @@ -0,0 +1,35 @@ +/* + * @lc app=leetcode id=114 lang=javascript + * + * [114] Flatten Binary Tree to Linked List + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function preorderTraversal(root) { + if (!root) return []; + + return [root] + .concat(preorderTraversal(root.left)) + .concat(preorderTraversal(root.right)); +} +/** + * @param {TreeNode} root + * @return {void} Do not return anything, modify root in-place instead. + */ +var flatten = function(root) { + if (root === null) return root; + const res = preorderTraversal(root); + + let curPos = 0; + let curNode = null; + + while(curNode = res[curPos]) { + curNode.left = null; + curNode.right = res[++curPos]; + } +}; diff --git a/daily/answers/134.gas-station.js b/daily/answers/134.gas-station.js new file mode 100644 index 0000000..6f7ea30 --- /dev/null +++ b/daily/answers/134.gas-station.js @@ -0,0 +1,61 @@ +/* + * @lc app=leetcode id=134 lang=javascript + * + * [134] Gas Station + */ + +function getIndex(index, n) { + if (index > n - 1) { + return index - n; + } + return index; +} +/** + * @param {number[]} gas + * @param {number[]} cost + * @return {number} + */ +var canCompleteCircuit = function(gas, cost) { + // bad 时间复杂度O(n^2) + // let remain = 0; + // const n = gas.length; + // for (let i = 0; i < gas.length; i++) { + // remain += gas[i]; + // remain -= cost[i]; + // let count = 0; + // while (remain >= 0) { + // count++; + // if (count === n) return i; + // remain += gas[getIndex(i + count, n)]; + // remain -= cost[getIndex(i + count, n)]; + // } + // remain = 0; + // } + // return -1; + // better solution 时间复杂度O(n) + + const n = gas.length; + let total = 0; + let remain = 0; + let start = 0; + + for(let i = 0; i < n; i++) { + total += gas[i]; + total -= cost[i]; + + remain += gas[i]; + remain -= cost[i]; + + // 如果remain < 0, 说明从start到i走不通 + // 并且从start到i走不通,那么所有的solution中包含start到i的肯定都走不通 + // 因此我们重新从i + 1开始作为start + if (remain < 0) { + remain = 0; + start = i + 1; + } + } + // 事实上,我们遍历一遍,也就确定了每一个元素作为start是否可以走完一圈 + + // 如果cost总和大于gas总和,无论如何也无法走到终点 + return total >= 0 ? start : -1; +}; diff --git a/daily/answers/14.longest-common-prefix.js b/daily/answers/14.longest-common-prefix.js new file mode 100644 index 0000000..94975a7 --- /dev/null +++ b/daily/answers/14.longest-common-prefix.js @@ -0,0 +1,44 @@ +/* + * @lc app=leetcode id=14 lang=javascript + * + * [14] Longest Common Prefix + */ + +function isCommonPrefix(strs, middle) { + const prefix = strs[0].substring(0, middle); + for (let i = 1; i < strs.length; i++) { + if (!strs[i].startsWith(prefix)) return false; + } + + return true; +} +/** + * @param {string[]} strs + * @return {string} + */ +var longestCommonPrefix = function(strs) { + // trie 解法 + // 时间复杂度O(m) 空间复杂度O(m * n) + + // tag: 二分法 + // 时间复杂度 O(n*logm) 空间复杂度O(1) + if (strs.length === 0) return ""; + if (strs.length === 1) return strs[0]; + + let minLen = Number.MAX_VALUE; + + for (let i = 0; i < strs.length; i++) { + minLen = Math.min(minLen, strs[i].length); + } + + let low = 0; + let high = minLen; + + while (low <= high) { + const middle = (low + high) >> 1; + if (isCommonPrefix(strs, middle)) low = middle + 1; + else high = middle - 1; + } + + return strs[0].substring(0, (low + high) >> 1); +}; diff --git a/daily/answers/17.letter-combinations-of-a-phone-number.js b/daily/answers/17.letter-combinations-of-a-phone-number.js new file mode 100644 index 0000000..9caa256 --- /dev/null +++ b/daily/answers/17.letter-combinations-of-a-phone-number.js @@ -0,0 +1,44 @@ +/* + * @lc app=leetcode id=17 lang=javascript + * + * [17] Letter Combinations of a Phone Number + */ +function backtrack(list, tempList, digits, start, keys) { + if (tempList.length === digits.length) { + return list.push(tempList.join('')); + } + + for (let i = start; i < digits.length; i++) { + const chars = keys[digits[i]]; + for (let j = 0; j < chars.length; j++) { + tempList.push(chars[j]); + backtrack(list, tempList, digits, i + 1, keys); + tempList.pop(); + } + } +} +/** + * @param {string} digits + * @return {string[]} + */ +var letterCombinations = function(digits) { + if (digits.length === 0) return []; + // Input:Digit string "23" + // Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. + const keys = [ + "", + "", + "abc", + "def", + "ghi", + "jkl", + "mno", + "pqrs", + "tuv", + "wxyz" + ]; + + const list = []; + backtrack(list, [], digits, 0, keys); + return list; +}; diff --git a/daily/answers/4.median-of-two-sorted-arrays.js b/daily/answers/4.median-of-two-sorted-arrays.js new file mode 100644 index 0000000..e469ee2 --- /dev/null +++ b/daily/answers/4.median-of-two-sorted-arrays.js @@ -0,0 +1,33 @@ +/* + * @lc app=leetcode id=4 lang=javascript + * + * [4] Median of Two Sorted Arrays + */ +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number} + */ +function findKth(nums1, nums2, k) { + if (nums1.length === 0) return nums2[k - 1]; + if (nums2.length === 0) return nums1[k - 1]; + if (k == 1) return Math.min(nums1[0], nums2[0]); + let i = Math.min(k >> 1, nums1.length); + let j = Math.min(k >> 1, nums2.length); + if (nums1[i - 1] > nums2[j - 1]) { + return findKth(nums1, nums2.slice(j), k - j); + } + + return findKth(nums1.slice(i), nums2, k - i); +} +var findMedianSortedArrays = function(nums1, nums2) { + // 1 + // 2 3 4 5 + const m = nums1.length, + n = nums2.length; + return ( + (findKth(nums1, nums2, (m + n + 1) >> 1) + + findKth(nums1, nums2, (m + n + 2) >> 1)) / + 2.0 + ); +}; diff --git a/daily/answers/448.find-all-numbers-disappeared-in-an-array.js b/daily/answers/448.find-all-numbers-disappeared-in-an-array.js new file mode 100644 index 0000000..47ee64b --- /dev/null +++ b/daily/answers/448.find-all-numbers-disappeared-in-an-array.js @@ -0,0 +1,27 @@ +/* + * @lc app=leetcode id=448 lang=javascript + * + * [448] Find All Numbers Disappeared in an Array + */ +/** + * @param {number[]} nums + * @return {number[]} + */ +var findDisappearedNumbers = function(nums) { + const res = []; + let cur = 0; + for(let i = 0; i < nums.length; i++) { + res[nums[i]] = true; + } + + for(let i = 0; i < nums.length; i++) { + if (res[i + 1] === void 0) { + res[cur++] = i + 1; + } + } + + res.length = cur; + + return res; +}; + diff --git a/daily/answers/460.lfu-cache.js b/daily/answers/460.lfu-cache.js new file mode 100644 index 0000000..d911327 --- /dev/null +++ b/daily/answers/460.lfu-cache.js @@ -0,0 +1,87 @@ +/* + * @lc app=leetcode id=460 lang=javascript + * + * [460] LFU Cache + */ +/** + * @param {number} capacity + */ +var LFUCache = function(capacity) { + this.capacity = capacity; + this.size = 0; + this.cache = {}; + this.timestamp = 0; +}; + +/** + * @param {number} key + * @return {number} + */ +LFUCache.prototype.get = function(key) { + const hit = this.cache[key]; + + if (hit === void 0) { + return -1; + } + hit.count += 1; + hit.timestamp = this.timestamp++; + + return hit.value; +}; + +// 时间复杂度O(n) n其实就是capacity +LFUCache.prototype.evicted = function() { + // evicted lfu + let leastCountKey = null; + let min = Number.MAX_VALUE; + + for (const k in this.cache) { + const item = this.cache[k]; + if (item.count < min) { + leastCountKey = k; + min = item.count; + } else if ( + item.count === min && + item.timestamp < this.cache[leastCountKey].timestamp + ) { + leastCountKey = k; + min = item.count; + } + } + + delete this.cache[leastCountKey]; + this.size--; +}; + +/** + * @param {number} key + * @param {number} value + * @return {void} + */ +LFUCache.prototype.put = function(key, value) { + if (this.capacity === 0) return; + const hit = this.cache[key]; + + if (hit === void 0) { + if (this.capacity === this.size) { + this.evicted(); + } + this.size++; + return (this.cache[key] = { + value, + timestamp: this.timestamp++, + count: 1 + }); + } + + this.cache[key].value = value; + this.cache[key].timestamp = this.timestamp++; + return (this.cache[key].count += 1); +}; + +/** + * Your LFUCache object will be instantiated and called as such: + * var obj = new LFUCache(capacity) + * var param_1 = obj.get(key) + * obj.put(key,value) + */ diff --git a/daily/answers/54.spiral-matrix.js b/daily/answers/54.spiral-matrix.js new file mode 100644 index 0000000..bff82e1 --- /dev/null +++ b/daily/answers/54.spiral-matrix.js @@ -0,0 +1,40 @@ +/* + * @lc app=leetcode id=54 lang=javascript + * + * [54] Spiral Matrix + */ +/** + * @param {number[][]} matrix + * @return {number[]} + */ +var spiralOrder = function(matrix) { + // https://leetcode.com/problems/spiral-matrix/discuss/20570/Clean-Java-readable-human-friendly-code + // brilliant! + const res = []; + if (matrix.length == 0) return res; + + let top = 0; + let bottom = matrix.length - 1; + let left = 0; + let right = matrix[0].length - 1; + + while (true) { + for (let i = left; i <= right; i++) res.push(matrix[top][i]); + top++; + if (top > bottom) break; + + for (let i = top; i <= bottom; i++) res.push(matrix[i][right]); + right--; + if (left > right) break; + + for (let i = right; i >= left; i--) res.push(matrix[bottom][i]); + bottom--; + if (top > bottom) break; + + for (let i = bottom; i >= top; i--) res.push(matrix[i][left]); + left++; + if (left > right) break; + } + + return res; +}; diff --git a/daily/answers/594.longest-harmonious-subsequence.js b/daily/answers/594.longest-harmonious-subsequence.js new file mode 100644 index 0000000..db9642c --- /dev/null +++ b/daily/answers/594.longest-harmonious-subsequence.js @@ -0,0 +1,33 @@ +/* + * @lc app=leetcode id=594 lang=javascript + * + * [594] Longest Harmonious Subsequence + */ +/** + * @param {number[]} nums + * @return {number} + */ +var findLHS = function(nums) { + // Input: [1,3,2,2,5,2,3,7] + // Output: 5 + // Explanation: The longest harmonious subsequence is [3,2,2,2,3]. + if (nums.length === 0) return 0; + const counts = {}; + let res = 0; + + for (let i = 0; i < nums.length; i++) { + if (!counts[nums[i]]) { + counts[nums[i]] = 1; + } else { + counts[nums[i]] += 1; + } + } + + for (let i = 0; i < nums.length; i++) { + if (counts[nums[i] + 1]) { + res = Math.max(res, counts[nums[i]] + counts[nums[i] + 1]); + } + } + + return res; +}; diff --git a/daily/answers/617.merge-two-binary-trees.js b/daily/answers/617.merge-two-binary-trees.js new file mode 100644 index 0000000..6da8de0 --- /dev/null +++ b/daily/answers/617.merge-two-binary-trees.js @@ -0,0 +1,26 @@ +/* + * @lc app=leetcode id=617 lang=javascript + * + * [617] Merge Two Binary Trees + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} t1 + * @param {TreeNode} t2 + * @return {TreeNode} + */ +var mergeTrees = function(t1, t2) { + // 递归,由于树是一种递归的数据结构,因此递归是符合直觉且比较简单的 + if (t1 === null) return t2; + if (t2 === null) return t1; + t1.val += t2.val; + t1.left = mergeTrees(t1.left, t2.left); + t1.right = mergeTrees(t1.right, t2.right); + return t1; +}; diff --git a/daily/answers/647.palindromic-substrings.js b/daily/answers/647.palindromic-substrings.js new file mode 100644 index 0000000..1588af3 --- /dev/null +++ b/daily/answers/647.palindromic-substrings.js @@ -0,0 +1,72 @@ +/* + * @lc app=leetcode id=647 lang=javascript + * + * [647] Palindromic Substrings + */ + +function isPalindromic(s) { + let start = 0; + let end = s.length - 1; + + while (start < end && s[start] === s[end]) { + start++; + end--; + } + + return start >= end; +} + +/** + * + * @param {对称点1} i + * @param {对称点2} j + * @param {原始字符串} s + * @return {以i,j为对称点的字符串s有多少回文串} count + */ +function extendPalindromic(i, j, s) { + const n = s.length; + let count = 0; + let start = i; + let end = j; + while (s[start] === s[end] && (start >= 0) && (end < n)) { + start--; + end++; + count++; + } + + return count; +} +/** + * @param {string} s + * @return {number} + */ +var countSubstrings = function(s) { + // "aaa" + // "abc" + // // 暴力法,空间复杂度O(1) 时间复杂度O(n^3) + // let count = s.length; + + // for(let i = 0; i < s.length - 1; i++) { + // for(let j = i + 1; j < s.length; j++) { + // if (isPalindromic(s.substring(i, j + 1))) { + // count++; + // } + // } + // } + + // return count; + + // 中心扩展法(运用回文的对称性) + // 时间复杂度O(n^2) 空间复杂度O(1) + const n = s.length; + let count = 0; + + for (let i = 0; i < n; i++) { + // 以 字符s[i]为对称点,一共有多少回文字串 + count += extendPalindromic(i, i, s); + // 以 字符s[i]和s[i+1]为对称点,一共有多少回文字串 + count += extendPalindromic(i, i + 1, s); + } + + return count; +}; diff --git a/daily/answers/687.longest-univalue-path.js b/daily/answers/687.longest-univalue-path.js new file mode 100644 index 0000000..32845b7 --- /dev/null +++ b/daily/answers/687.longest-univalue-path.js @@ -0,0 +1,38 @@ +/* + * @lc app=leetcode id=687 lang=javascript + * + * [687] Longest Univalue Path + */ + +// 返回经过root的且只能取左右一个节点的路径长度 +function helper(node, res) { + if (node === null) return 0; + const l = helper(node.left, res); + const r = helper(node.right, res); + let lcnt = 0; + let rcnt = 0; + if (node.left && node.val === node.left.val) lcnt = lcnt + l + 1; + if (node.right && node.val === node.right.val) rcnt = rcnt + r + 1; + + res.max = Math.max(res.max, lcnt + rcnt); + + return Math.max(lcnt, rcnt); + } + /** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ + /** + * @param {TreeNode} root + * @return {number} + */ + var longestUnivaluePath = function(root) { + const res = { + max: 0 + }; + helper(root, res); + return res.max; + }; \ No newline at end of file diff --git a/daily/answers/739.daily-temperatures.js b/daily/answers/739.daily-temperatures.js new file mode 100644 index 0000000..48b895e --- /dev/null +++ b/daily/answers/739.daily-temperatures.js @@ -0,0 +1,42 @@ +/* + * @lc app=leetcode id=739 lang=javascript + * + * [739] Daily Temperatures + */ +/** + * @param {number[]} T + * @return {number[]} + */ +var dailyTemperatures = function(T) { + // // 暴力 时间复杂度O(n^2), 空间复杂度O(1) + // const res = []; + // for(let i = 0; i < T.length; i++) { + // res[i] = 0; + // for(let j = i; j < T.length; j++) { + // if (T[j] > T[i]) { + // res[i] = j - i; + // break; + // } + // } + // } + + // return res; + + // 递增栈/递减栈 + // 这里我们需要用到递减栈 + // 时间复杂度O(n), 空间复杂度O(n) + // 典型的空间换时间 + const stack = []; + const res = []; + + for (let i = 0; i < T.length; i++) { + res[i] = 0; + while (stack.length !== 0 && T[i] > T[stack[stack.length - 1]]) { + const peek = stack.pop(); + res[peek] = i - peek; + } + stack.push(i); + } + + return res; +}; diff --git a/daily/answers/744.find-smallest-letter-greater-than-target.js b/daily/answers/744.find-smallest-letter-greater-than-target.js new file mode 100644 index 0000000..e7e004f --- /dev/null +++ b/daily/answers/744.find-smallest-letter-greater-than-target.js @@ -0,0 +1,26 @@ +/* + * @lc app=leetcode id=744 lang=javascript + * + * [744] Find Smallest Letter Greater Than Target + */ +/** + * @param {character[]} letters + * @param {character} target + * @return {character} + */ +var nextGreatestLetter = function(letters, target) { + let start = 0; + let end = letters.length - 1; + + while(start < end) { + const mid = start + ((end - start) >> 1); + if (letters[mid] <= target) { + start = mid + 1; + } else { + end = mid; + } + } + // 题目要求找不到的时候,就返回第一个元素(好诡异啊) + return letters[end] > target ? letters[end] : letters[0]; +}; + diff --git a/daily/answers/950.reveal-cards-in-increasing-order.js b/daily/answers/950.reveal-cards-in-increasing-order.js new file mode 100644 index 0000000..dc38af3 --- /dev/null +++ b/daily/answers/950.reveal-cards-in-increasing-order.js @@ -0,0 +1,24 @@ +/* + * @lc app=leetcode id=950 lang=javascript + * + * [950] Reveal Cards In Increasing Order + */ +/** + * @param {number[]} deck + * @return {number[]} + */ +var deckRevealedIncreasing = function(deck) { + const hand = []; + const table = deck.sort((a, b) => a - b); + + let handTurn = true; + while (table.length > 0) { + if (handTurn) { + hand.unshift(table.pop()); + } else { + hand.unshift(hand.pop()); + } + handTurn = !handTurn; + } + return hand; +}; diff --git a/daily/answers/three-doors-problem.js b/daily/answers/three-doors-problem.js new file mode 100644 index 0000000..a3e75b2 --- /dev/null +++ b/daily/answers/three-doors-problem.js @@ -0,0 +1,16 @@ +// true 代表换之后赢了 +// false 代表换了之后输了 +function threeDoors() { + const doors = [0, 0, 1]; + const random = Math.random() * doors.length; + const pos = Math.floor(random); + if (doors[pos]) return false; + console.count(pos); + return true; +} + +const times = 1000000; +for(let i = 0; i < times; i++) { + const win = threeDoors(); + console.count(win); +} diff --git a/donation.md b/donation.md new file mode 100644 index 0000000..afcf4e6 --- /dev/null +++ b/donation.md @@ -0,0 +1,6 @@ +> 如果名单中漏掉了你的信息,请联系我微信:DevelopeEngineer 。另外,有捐赠过的同学,我这边联系不到你,如果你希望将你的信息展示出来,也请联系我。 + +感谢以下捐赠者,我目前没有在任何平台卖钱,用郭德纲的话叫:“我给你快乐,你给我饭吃”,我就只能说:“我给你知识,你给我买咖啡☕️的钱” + +- 【前端迷】 - ¥88 + diff --git a/problems/1.TwoSum.en.md b/problems/1.TwoSum.en.md new file mode 100644 index 0000000..4e80118 --- /dev/null +++ b/problems/1.TwoSum.en.md @@ -0,0 +1,50 @@ +## Problem +https://leetcode-cn.com/problems/two-sum + +## Problem Description +``` +Given an array of integers, return indices of the two numbers such that they add up to a specific target. + +You may assume that each input would have exactly one solution, and you may not use the same element twice. + +Example: + +Given nums = [2, 7, 11, 15], target = 9, + +Because nums[0] + nums[1] = 2 + 7 = 9, +return [0, 1]. +``` + +## Solution +The easiest solution to come up with is Brute Force. We could write two for-loops to traverse every element, and find the target numbers that meet the requirement. However, the time complexity of this solution is O(N^2), while the space complexity is O(1). Apparently, we need to find a way to optimize this solution since the time complexity is too high. What we could do is to record the numbers we have traversed and the relevant index with a Map. Whenever we meet a new number during traversal, we go back to the Map and check whether the `diff` between this number and the target number appeared before. If it did, the problem has been solved and there's no need to continue. + +## Key Points + - Find the difference instead of the sum + - Connect every number with its index through the help of Map + - Less time by more space. Reduce the time complexity from O(N) to O(1) + + ## Code + - Support Language: JS + +```js +/** + * @param {number[]} nums + * @param {number} target + * @return {number[]} + */ +const twoSum = function (nums, target) { + const map = new Map(); + for (let i = 0; i < nums.length; i++) { + const diff = target - nums[i]; + if (map.has(diff)) { + return [map.get(diff), i]; + } + map.set(nums[i], i); + } +} +``` + +***Complexity Anlysis*** + +- *Time Complexity*: O(N) +- *Space Complexity*:O(N) diff --git a/problems/1.TwoSum.md b/problems/1.TwoSum.md new file mode 100644 index 0000000..2f82994 --- /dev/null +++ b/problems/1.TwoSum.md @@ -0,0 +1,50 @@ +## 题目地址 +https://leetcode-cn.com/problems/two-sum + +## 题目描述 +``` +给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 + +你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 + +示例: + +给定 nums = [2, 7, 11, 15], target = 9 + +因为 nums[0] + nums[1] = 2 + 7 = 9 +所以返回 [0, 1] +``` +## 思路 +最容易想到的就是暴力枚举,我们可以利用两层 for 循环来遍历每个元素,并查找满足条件的目标元素。不过这样时间复杂度为 O(N^2),空间复杂度为 O(1),时间复杂度较高,我们要想办法进行优化。我们可以增加一个 Map 记录已经遍历过的数字及其对应的索引值。这样当遍历一个新数字的时候去 Map 里查询,target 与该数的差值是否已经在前面的数字中出现过。如果出现过,那么已经得出答案,就不必再往下执行了。 + +## 关键点 + +- 求和转换为求差 +- 借助 Map 结构将数组中每个元素及其索引相互对应 +- 以空间换时间,将查找时间从 O(N) 降低到 O(1) + +## 代码 +* 语言支持:JS + +```js +/** + * @param {number[]} nums + * @param {number} target + * @return {number[]} + */ +const twoSum = function (nums, target) { + const map = new Map(); + for (let i = 0; i < nums.length; i++) { + const diff = target - nums[i]; + if (map.has(diff)) { + return [map.get(diff), i]; + } + map.set(nums[i], i); + } +} +``` + +***复杂度分析*** + +- 时间复杂度:O(N) +- 空间复杂度:O(N) diff --git a/problems/1011.capacity-to-ship-packages-within-d-days-cn.md b/problems/1011.capacity-to-ship-packages-within-d-days-cn.md new file mode 100644 index 0000000..5e257be --- /dev/null +++ b/problems/1011.capacity-to-ship-packages-within-d-days-cn.md @@ -0,0 +1,167 @@ +## 题目地址(1011. 在 D 天内送达包裹的能力) + +https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days + +## 题目描述 + +传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。 + +传送带上的第 i  个包裹的重量为  weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。 + +返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。 + +示例 1: + +输入:weights = [1,2,3,4,5,6,7,8,9,10], D = 5 +输出:15 +解释: +船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示: +第 1 天:1, 2, 3, 4, 5 +第 2 天:6, 7 +第 3 天:8 +第 4 天:9 +第 5 天:10 + +请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。 +示例 2: + +输入:weights = [3,2,2,4,1,4], D = 3 +输出:6 +解释: +船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示: +第 1 天:3, 2 +第 2 天:2, 4 +第 3 天:1, 4 +示例 3: + +输入:weights = [1,2,3,1,1], D = 4 +输出:3 +解释: +第 1 天:1 +第 2 天:2 +第 3 天:3 +第 4 天:1, 1 + +提示: + +1 <= D <= weights.length <= 50000 +1 <= weights[i] <= 500 + +## 思路 + +这道题和[猴子吃香蕉](https://github.com/azl397985856/leetcode/blob/master/problems/875.koko-eating-bananas.md) 简直一摸一样,没有看过的建议看一下那道题。 + +像这种题如何你能发现本质的考点,那么 AC 是瞬间的事情。 这道题本质上就是从 1,2,3,4,。。。total(其中 toal 是总的货物重量)的有限离散数据中查找给定的数。这里我们不是直接查找 target,而是查找恰好能够在 D 天运完的载货量。 + +- 容量是 1 可以运完么? +- 容量是 2 可以运完么? +- 容量是 3 可以运完么? +- 。。。 +- 容量是 total 可以运完么?(当然可以,因为 D 大于等于 1) + +上面不断询问的过程如果回答是 yes 我们直接 return 即可。如果回答是 no,我们继续往下询问。 + +这是一个典型的二分问题,只不过我们的判断条件略有不同,大概是: + +```python +def canShip(opacity): + # 指定船的容量是否可以在D天运完 + lo = 0 + hi = total + while lo < hi: + mid = (lo + hi) // 2 + if canShip(mid): + hi = mid + else: + lo = mid + 1 + + return lo +``` + +## 关键点解析 + +- 能够识别出是给定的有限序列查找一个数字(二分查找),要求你对二分查找以及变体十分熟悉 + +## 代码 + +* 语言支持:`JS`,`Python` + +`python`: + +```python +class Solution: + def shipWithinDays(self, weights: List[int], D: int) -> int: + lo = 0 + hi = 0 + + def canShip(opacity): + days = 1 + remain = opacity + for weight in weights: + if weight > opacity: + return False + remain -= weight + if remain < 0: + days += 1 + remain = opacity - weight + return days <= D + + for weight in weights: + hi += weight + while lo < hi: + mid = (lo + hi) // 2 + if canShip(mid): + hi = mid + else: + lo = mid + 1 + + return lo +``` + +`js`: + +```js +/** + * @param {number[]} weights + * @param {number} D + * @return {number} + */ +var shipWithinDays = function(weights, D) { + let high = weights.reduce((acc, cur) => acc + cur) + let low = 0 + + while(low < high) { + let mid = Math.floor((high + low) / 2) + if (canShip(mid)) { + high = mid + } else { + low = mid + 1 + } + } + + return low + + function canShip(opacity) { + let remain = opacity + let count = 1 + for (let weight of weights) { + if (weight > opacity) { + return false + } + remain -= weight + if (remain < 0) { + count++ + remain = opacity - weight + } + if (count > D) { + return false + } + } + return count <= D + } +}; +``` + +## 扩展 + +## 参考 diff --git a/problems/1011.capacity-to-ship-packages-within-d-days-en.md b/problems/1011.capacity-to-ship-packages-within-d-days-en.md new file mode 100644 index 0000000..a313d6e --- /dev/null +++ b/problems/1011.capacity-to-ship-packages-within-d-days-en.md @@ -0,0 +1,187 @@ +## Problem + +https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days + +## Problem Description + +A conveyor belt has packages that must be shipped from one port to another within D days. + +The i-th package on the conveyor belt has a weight of weights[i]. Each day, we load the ship with packages on the conveyor belt (in the order given by weights). We may not load more weight than the maximum weight capacity of the ship. + +Return the least weight capacity of the ship that will result in all the packages on the conveyor belt being shipped within D days. + +**Example 1:** + +``` +Input: weights = [1,2,3,4,5,6,7,8,9,10], D = 5 +Output: 15 +Explanation: +A ship capacity of 15 is the minimum to ship all the packages in 5 days like this: +1st day: 1, 2, 3, 4, 5 +2nd day: 6, 7 +3rd day: 8 +4th day: 9 +5th day: 10 + +Note that the cargo must be shipped in the order given, so using a ship of capacity 14 and splitting the packages into parts like (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) is not allowed. +``` + +**Example 2:** + +``` +Input: weights = [3,2,2,4,1,4], D = 3 +Output: 6 +Explanation: +A ship capacity of 6 is the minimum to ship all the packages in 3 days like this: +1st day: 3, 2 +2nd day: 2, 4 +3rd day: 1, 4 +``` + +**Example 3:** + +``` +Input: weights = [1,2,3,1,1], D = 4 +Output: 3 +Explanation: +1st day: 1 +2nd day: 2 +3rd day: 3 +4th day: 1, 1 +``` + + + + **Note:** + +1. `1 <= D <= weights.length <= 50000` +2. `1 <= weights[i] <= 500` + + + +## Solution + +The problem is same as [**LeetCode 875 koko-eating-bananas**] (https://github.com/azl397985856/leetcode/blob/master/problems/875.koko-eating-bananas-en.md) practically. + +It is easy to solve this kind of problems if you take a closer look into it. + + +The essence is to search a given number in finite discrete data like [ 1,2,3,4, ... , total ]. + +However, We should find the cargo that can be shipped in D days rather than look for the target directly. + + +Consider the following questions: + +- Can it be shipped if the capacity is 1? +- Can it be shipped if the capacity is 2? +- Can it be shipped if the capacity is 3? +- ... +- Can it be shipped if the capacity is total ? ( Yeap we can, D is greater than or equal to 1) + +During the process, we directly `return` if the answer is *yes*. + +If the answer is *no*, just keep asking. + +This is a typical binary search problem, the only difference is the judgement condition: + + +```python +def canShip(opacity): + # Whether the capacity of the specified ship can be shipped in D days + lo = 0 + hi = total + while lo < hi: + mid = (lo + hi) // 2 + if canShip(mid): + hi = mid + else: + lo = mid + 1 + return lo +``` + +## Key Points + +- if you are so familiar with binary search as well its transformation, you can easily find out that using binary search to find one number in a given number sequence of a certain length. + +## Code (`JS/Python`) + +- `Python` + +```python +class Solution: + def shipWithinDays(self, weights: List[int], D: int) -> int: + lo = 0 + hi = 0 + + def canShip(opacity): + days = 1 + remain = opacity + for weight in weights: + if weight > opacity: + return False + remain -= weight + if remain < 0: + days += 1 + remain = opacity - weight + return days <= D + + for weight in weights: + hi += weight + while lo < hi: + mid = (lo + hi) // 2 + if canShip(mid): + hi = mid + else: + lo = mid + 1 + + return lo +``` + +- `JavaScript` + +```js +/** + * @param {number[]} weights + * @param {number} D + * @return {number} + */ +var shipWithinDays = function(weights, D) { + let high = weights.reduce((acc, cur) => acc + cur) + let low = 0 + + while(low < high) { + let mid = Math.floor((high + low) / 2) + if (canShip(mid)) { + high = mid + } else { + low = mid + 1 + } + } + + return low + + function canShip(opacity) { + let remain = opacity + let count = 1 + for (let weight of weights) { + if (weight > opacity) { + return false + } + remain -= weight + if (remain < 0) { + count++ + remain = opacity - weight + } + if (count > D) { + return false + } + } + return count <= D + } +}; +``` + +## References + +## Extension diff --git a/problems/1014.best-sightseeing-pair.md b/problems/1014.best-sightseeing-pair.md new file mode 100644 index 0000000..45caa61 --- /dev/null +++ b/problems/1014.best-sightseeing-pair.md @@ -0,0 +1,92 @@ +## 题目地址(1014. 最佳观光组合) + +https://leetcode-cn.com/problems/best-sightseeing-pair/description/ + +## 题目描述 + +给定正整数数组  A,A[i]  表示第 i 个观光景点的评分,并且两个景点  i 和  j  之间的距离为  j - i。 + +一对景点(i < j)组成的观光组合的得分为(A[i] + A[j] + i - j):景点的评分之和减去它们两者之间的距离。 + +返回一对观光景点能取得的最高分。 + +示例: + +输入:[8,1,5,2,6] +输出:11 +解释:i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11 + +提示: + +2 <= A.length <= 50000 +1 <= A[i] <= 1000 + +## 思路 + +最简单的思路就是两两组合,找出最大的,妥妥超时,我们来看下代码: + +```python +class Solution: + def maxScoreSightseeingPair(self, A: List[int]) -> int: + n = len(A) + res = 0 + for i in range(n - 1): + for j in range(i + 1, n): + res = max(res, A[i] + A[j] + i - j) + return res +``` + +我们思考如何优化。 其实我们可以遍历一遍数组,对于数组的每一项`A[j] - j` 我们都去前面找`最大`的 A[i] + i (这样才能保证结果最大)。 + +我们考虑使用动态规划来解决, 我们使用 dp[i] 来表示 数组 A 前 i 项的`A[i] + i`的最大值。 + +```python +class Solution: + def maxScoreSightseeingPair(self, A: List[int]) -> int: + n = len(A) + dp = [float('-inf')] * (n + 1) + res = 0 + for i in range(n): + dp[i + 1] = max(dp[i], A[i] + i) + res = max(res, dp[i] + A[i] - i) + return res +``` + +如上其实我们发现,dp[i + 1] 只和 dp[i] 有关,这是一个空间优化的信号。我们其实可以使用一个变量来记录,而不必要使用一个数组,代码见下方。 + +## 关键点解析 + +- 空间换时间 +- dp 空间优化 + +## 代码 + +```python +class Solution: + def maxScoreSightseeingPair(self, A: List[int]) -> int: + n = len(A) + pre = A[0] + 0 + res = 0 + for i in range(1, n): + res = max(res, pre + A[i] - i) + pre = max(pre, A[i] + i) + return res +``` + +## 小技巧 + +Python 的代码如果不使用 max,而是使用 if else 效率目测会更高,大家可以试一下。 + +```python +class Solution: + def maxScoreSightseeingPair(self, A: List[int]) -> int: + n = len(A) + pre = A[0] + 0 + res = 0 + for i in range(1, n): + # res = max(res, pre + A[i] - i) + # pre = max(pre, A[i] + i) + res = res if res > pre + A[i] - i else pre + A[i] - i + pre = pre if pre > A[i] + i else A[i] + i + return res +``` diff --git a/problems/1015.smallest-integer-divisible-by-k.md b/problems/1015.smallest-integer-divisible-by-k.md new file mode 100644 index 0000000..bbfca68 --- /dev/null +++ b/problems/1015.smallest-integer-divisible-by-k.md @@ -0,0 +1,100 @@ +## 题目地址 + +https://leetcode-cn.com/problems/smallest-integer-divisible-by-k/description/ + +## 题目描述 + +``` +给定正整数 K,你需要找出可以被 K 整除的、仅包含数字 1 的最小正整数 N。 + +返回 N 的长度。如果不存在这样的 N,就返回 -1。 + +  + +示例 1: + +输入:1 +输出:1 +解释:最小的答案是 N = 1,其长度为 1。 +示例 2: + +输入:2 +输出:-1 +解释:不存在可被 2 整除的正整数 N 。 +示例 3: + +输入:3 +输出:3 +解释:最小的答案是 N = 111,其长度为 3。 +  + +提示: + +1 <= K <= 10^5 + +``` + +## 思路 + +这道题是说给定一个 K 值,能否找到一个形如 1,11,111,1111 。。。 这样的数字 n 使得 n % K == 0。 + +首先容易想到的是如果 K 是 2,4,5, 6,8 结尾的话,一定是不行的。直观的解法是从 1,11,111,1111 。。。 这样一直除下去,直到碰到可以整除的,我们返回即可。 但是如果这个数字根本就无法整除怎么办?没错,我们会无限循环下去。我们应该在什么时刻跳出循环,返回 - 1 (表示不能整除)呢? + +我们拿题目给出的不能整除的 2 来说。 + +- 1 // 2 等于 1 +- 11 // 2 等于 1 +- 111 // 2 等于 1 +- ... + +我们再来一个不能整除的例子 6: + +- 1 // 6 等于 1 +- 11 // 6 等于 5 +- 111 // 6 等于 3 +- 1111 // 6 等于 1 +- 11111 // 6 等于 5 +- ... + +通过观察我们发现不断整除的过程,会陷入无限循环,对于 2 来说,其循环节就是 1。对于 6 来说,其循环节来说就是 153。而且由于我们的分母是 6,也就是说余数的可能性一共只有六种情况 0,1,2,3,4,5。 + +上面是感性的认识, 接下来我们从数学上予以证明。上面的算法用公式来表示就是`mod = (10 \* mod + 1) % K`。假如出现了相同的数,我们可以肯定之后会无限循环。比如 153 之后出现了 1,我们可以肯定之后一定是 35。。。 因为我们的 mod 只是和前一个 mod 有关,上面的公式是一个`纯函数`。 + +## 关键点解析 + +- 数学(无限循环与循环节) + +## 代码 + +```python +# +# @lc app=leetcode.cn id=1015 lang=python3 +# +# [1015] 可被 K 整除的最小整数 +# + +# @lc code=start + + +class Solution: + def smallestRepunitDivByK(self, K: int) -> int: + if K % 10 in [2, 4, 5, 6, 8]: + return - 1 + seen = set() + mod = 0 + for i in range(1, K + 1): + mod = (mod * 10 + 1) % K + if mod in seen: + return -1 + if mod == 0: + return ix + seen.add(mod) + + # @lc code=end + + +``` + +## 相关题目 + +- [166. 分数到小数](https://leetcode-cn.com/problems/fraction-to-recurring-decimal/) diff --git a/problems/1019.next-greater-node-in-linked-list.md b/problems/1019.next-greater-node-in-linked-list.md new file mode 100644 index 0000000..964a378 --- /dev/null +++ b/problems/1019.next-greater-node-in-linked-list.md @@ -0,0 +1,102 @@ +## 题目地址 + +https://leetcode-cn.com/problems/next-greater-node-in-linked-list/submissions/ + +## 题目描述 + +``` +给出一个以头节点 head 作为第一个节点的链表。链表中的节点分别编号为:node_1, node_2, node_3, ... 。 + +每个节点都可能有下一个更大值(next larger value):对于 node_i,如果其 next_larger(node_i) 是 node_j.val,那么就有 j > i 且  node_j.val > node_i.val,而 j 是可能的选项中最小的那个。如果不存在这样的 j,那么下一个更大值为 0 。 + +返回整数答案数组 answer,其中 answer[i] = next_larger(node_{i+1}) 。 + +注意:在下面的示例中,诸如 [2,1,5] 这样的输入(不是输出)是链表的序列化表示,其头节点的值为 2,第二个节点值为 1,第三个节点值为 5 。 + +  + +示例 1: + +输入:[2,1,5] +输出:[5,5,0] +示例 2: + +输入:[2,7,4,3,5] +输出:[7,0,5,5,0] +示例 3: + +输入:[1,7,5,1,9,2,5,1] +输出:[7,9,9,9,0,5,0,0] +  + +提示: + +对于链表中的每个节点,1 <= node.val <= 10^9 +给定列表的长度在 [0, 10000] 范围内 +``` + +## 思路 + +看完题目就应该想到单调栈才行,LeetCode 上关于单调栈的题目还不少,难度都不小。但是一旦你掌握了这个算法,那么这些题目对你来说都不是问题了。 + +如果你不用单调栈,那么可以暴力$O(N^2)$的时间复杂度解决,只需要双层循环即可。但是这种做法应该是过不了关的。使用单调栈可以将时间复杂度降低到线性,当然需要额外的$O(N)$的空间复杂度。 + +顾名思义,单调栈即满足单调性的栈结构。与单调队列相比,其只在一端进行进出。为了描述方便,以下举例及代码以维护一个整数的单调递减栈为例。将一个元素插入单调栈时,为了维护栈的单调性,需要在保证将该元素插入到栈顶后整个栈满足单调性的前提下弹出最少的元素。 + +例如,栈中自顶向下的元素为 1,2,4,5 ,插入元素 3 时为了保证单调性需要依次弹出元素 : + +- 最开始栈是这样的: [5,4,2,1] +- 为了维护递减特性,1,2 需要被移除。此时栈是这样的: [5,4] +- 我们将 3 push 到栈顶即可 +- 此时栈是这样的: [5,4,3] + +用代码描述如下: + +Python Code: + +```python +def monoStack(list): + st = [] + for v in list: + while len(st) > 0 and v > st[-1]: + st.pop() + st.append(v) + return st +monoStack([5, 4, 2, 1, 3]) # output: [5, 4, 3] +``` + +## 关键点 + +- 单调栈(单调递减栈) +- 单调栈的代码模板 + +## 代码 + +Python Code: + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def nextLargerNodes(self, head): + res, st = [], [] + while head: + while len(st) > 0 and head.val > st[-1][1]: + res[st.pop()[0]] = head.val + st.append((len(res), head.val)) + res.append(0) + head = head.next + return res +``` + +## 扩展 + +甚至可以做到 O(1)的空间复杂度,请参考[C# O(n) time O(1) space]() + +## 相关题目 + +- [毎日一题 - 739.Daily Temperatures](https://github.com/azl397985856/leetcode/blob/master/daily/2019-06-06.md) diff --git a/problems/102.binary-tree-level-order-traversal.md b/problems/102.binary-tree-level-order-traversal.md new file mode 100644 index 0000000..747510e --- /dev/null +++ b/problems/102.binary-tree-level-order-traversal.md @@ -0,0 +1,246 @@ + +## 题目地址 +https://leetcode.com/problems/binary-tree-level-order-traversal/description/ + +## 题目描述 +``` +给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 + +  + +示例: +二叉树:[3,9,20,null,null,15,7], + + 3 + / \ + 9 20 + / \ + 15 7 +返回其层次遍历结果: + +[ + [3], + [9,20], + [15,7] +] +``` + +## 思路 + +这是一个典型的二叉树遍历问题, 关于二叉树遍历,我总结了一个[专题](https://github.com/azl397985856/leetcode/blob/master/thinkings/binary-tree-traversal.md),大家可以先去看下那个,然后再来刷这道题。 + +这道题可以借助`队列`实现,首先把root入队,然后入队一个特殊元素Null(来表示每层的结束)。 + + +然后就是while(queue.length), 每次处理一个节点,都将其子节点(在这里是left和right)放到队列中。 + +然后不断的出队, 如果出队的是null,则表式这一层已经结束了,我们就继续push一个null。 + +如果不入队特殊元素Null来表示每层的结束,则在while循环开始时保存当前队列的长度,以保证每次只遍历一层(参考下面的C++ Code)。 + +> 如果采用递归方式,则需要将当前节点,当前所在的level以及结果数组传递给递归函数。在递归函数中,取出节点的值,添加到level参数对应结果数组元素中(参考下面的C++ Code 或 Python Code)。 + + +## 关键点解析 + +- 队列 + +- 队列中用Null(一个特殊元素)来划分每层 + +- 树的基本操作- 遍历 - 层次遍历(BFS) + +- 注意塞入null的时候,判断一下当前队列是否为空,不然会无限循环 + + +## 代码 +* 语言支持:JS,C++,Python3 + +Javascript Code: +```js +/* + * @lc app=leetcode id=102 lang=javascript + * + * [102] Binary Tree Level Order Traversal + * + * https://leetcode.com/problems/binary-tree-level-order-traversal/description/ + * + * algorithms + * Medium (47.18%) + * Total Accepted: 346.4K + * Total Submissions: 731.3K + * Testcase Example: '[3,9,20,null,null,15,7]' + * + * Given a binary tree, return the level order traversal of its nodes' values. + * (ie, from left to right, level by level). + * + * + * For example: + * Given binary tree [3,9,20,null,null,15,7], + * + * + * ⁠ 3 + * ⁠ / \ + * ⁠ 9 20 + * ⁠ / \ + * ⁠ 15 7 + * + * + * + * return its level order traversal as: + * + * [ + * ⁠ [3], + * ⁠ [9,20], + * ⁠ [15,7] + * ] + * + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[][]} + */ +var levelOrder = function(root) { + if (!root) return []; + const items = []; // 存放所有节点 + const queue = [root, null]; // null 简化操作 + let levelNodes = []; // 存放每一层的节点 + + while (queue.length > 0) { + const t = queue.shift(); + + if (t) { + levelNodes.push(t.val) + if (t.left) { + queue.push(t.left); + } + if (t.right) { + queue.push(t.right); + } + } else { // 一层已经遍历完了 + items.push(levelNodes); + levelNodes = []; + if (queue.length > 0) { + queue.push(null) + } + } + } + + return items; +}; + +``` +C++ Code: +```C++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ + + // 迭代 +class Solution { +public: + vector> levelOrder(TreeNode* root) { + auto ret = vector>(); + if (root == nullptr) return ret; + auto q = vector(); + q.push_back(root); + auto level = 0; + while (!q.empty()) + { + auto sz = q.size(); + ret.push_back(vector()); + for (auto i = 0; i < sz; ++i) + { + auto t = q.front(); + q.erase(q.begin()); + ret[level].push_back(t->val); + if (t->left != nullptr) q.push_back(t->left); + if (t->right != nullptr) q.push_back(t->right); + } + ++level; + } + return ret; + } +}; + +// 递归 +class Solution { +public: + vector> levelOrder(TreeNode* root) { + vector> v; + levelOrder(root, 0, v); + return v; + } +private: + void levelOrder(TreeNode* root, int level, vector>& v) { + if (root == NULL) return; + if (v.size() < level + 1) v.resize(level + 1); + v[level].push_back(root->val); + levelOrder(root->left, level + 1, v); + levelOrder(root->right, level + 1, v); + } +}; +``` +Python Code: +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def levelOrder(self, root: TreeNode) -> List[List[int]]: + """递归法""" + if root is None: + return [] + + result = [] + + def add_to_result(level, node): + """递归函数 + :param level int 当前在二叉树的层次 + :param node TreeNode 当前节点 + """ + if level > len(result) - 1: + result.append([]) + + result[level].append(node.val) + if node.left: + add_to_result(level+1, node.left) + if node.right: + add_to_result(level+1, node.right) + + add_to_result(0, root) + return result +``` + +***复杂度分析*** +- 时间复杂度:$O(N)$,其中N为树中节点总数。 +- 空间复杂度:$O(N)$,其中N为树中节点总数。 + +更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经30K star啦。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +## 扩展 + +实际上这道题方法很多, 比如经典的三色标记法。 + +## 相关题目 +- [103.binary-tree-zigzag-level-order-traversal](./103.binary-tree-zigzag-level-order-traversal.md) +- [104.maximum-depth-of-binary-tree](./104.maximum-depth-of-binary-tree.md) diff --git a/problems/1020.number-of-enclaves.md b/problems/1020.number-of-enclaves.md new file mode 100644 index 0000000..bd77a25 --- /dev/null +++ b/problems/1020.number-of-enclaves.md @@ -0,0 +1,163 @@ +## 题目地址 + +https://leetcode-cn.com/problems/number-of-enclaves/ + +## 题目描述 + +``` +给出一个二维数组 A,每个单元格为 0(代表海)或 1(代表陆地)。 + +移动是指在陆地上从一个地方走到另一个地方(朝四个方向之一)或离开网格的边界。 + +返回网格中无法在任意次数的移动中离开网格边界的陆地单元格的数量。 + +  + +示例 1: + +输入:[[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]] +输出:3 +解释: +有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。 +示例 2: + +输入:[[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]] +输出:0 +解释: +所有 1 都在边界上或可以到达边界。 +  + +提示: + +1 <= A.length <= 500 +1 <= A[i].length <= 500 +0 <= A[i][j] <= 1 +所有行的大小都相同 + +``` + +## 解法一 (暴力法) + +### 思路 + +这是一个典型的可以使用 DFS 进行解决的一类题目, LeetCode 相关的题目有很多。 + +对于这种题目不管是思路还是代码都有很大的相似性,我们来看下。 + +暴力法的思路很简单,我们遍历整个矩阵: + +- 如果遍历到 0,我们不予理会 +- 如果遍历到 1. 我们将其加到 temp +- 我们不断拓展边界(上下左右) +- 如果 dfs 过程中碰到了边界,说明我们可以逃脱,我们将累加的 temp 清空 +- 如果 dfs 过程之后没有碰到边界,说明我们无法逃脱。我们将 temp 加到 cnt +- 最终返回 cnt 即可 + +### 关键点解析 + +- visited 记录访问过的节点,防止重复计算 + +### 代码 + +Python Code: + +```python +class Solution: + temp = 0 + meetEdge = False + + def numEnclaves(self, A: List[List[int]]) -> int: + cnt = 0 + m = len(A) + n = len(A[0]) + visited = set() + + def dfs(i, j): + if i < 0 or i >= m or j < 0 or j >= n or (i, j) in visited: + return + visited.add((i, j)) + if A[i][j] == 1: + self.temp += 1 + else: + return + if i == 0 or i == m - 1 or j == 0 or j == n - 1: + self.meetEdge = True + dfs(i + 1, j) + dfs(i - 1, j) + dfs(i, j + 1) + dfs(i, j - 1) + for i in range(m): + for j in range(n): + dfs(i, j) + if not self.meetEdge: + cnt += self.temp + self.meetEdge = False + self.temp = 0 + return cnt + +``` + +## 解法二 (消除法) + +### 思路 + +上面的解法时间复杂度和空间复杂度都很差,我们考虑进行优化, 这里我们使用消除法。 + +- 从矩阵边界开始 dfs +- 如果碰到 1 就将其变成 0 +- 如果碰到 0 则什么都不做 +- 最后我们遍历整个矩阵,数一下 1 的个数即可。 + +### 关键点解析 + +- dfs 消除法 + +### 代码 + +Python Code: + +```python +# +# @lc app=leetcode.cn id=1020 lang=python3 +# +# [1020] 飞地的数量 +# + +# @lc code=start + + +class Solution: + + def numEnclaves(self, A: List[List[int]]) -> int: + cnt = 0 + m = len(A) + n = len(A[0]) + + def dfs(i, j): + if i < 0 or i >= m or j < 0 or j >= n or A[i][j] == 0: + return + A[i][j] = 0 + + dfs(i + 1, j) + dfs(i - 1, j) + dfs(i, j + 1) + dfs(i, j - 1) + for i in range(m): + dfs(i, 0) + dfs(i, n - 1) + for j in range(1, n - 1): + dfs(0, j) + dfs(m - 1, j) + for i in range(m): + for j in range(n): + if A[i][j] == 1: + cnt += 1 + return cnt + + # @lc code=end + +``` + +## 参考 + +- [200.number-of-islands](https://github.com/azl397985856/leetcode/blob/master/problems/200.number-of-islands.md) diff --git a/problems/1023.camelcase-matching.md b/problems/1023.camelcase-matching.md new file mode 100644 index 0000000..cfeb796 --- /dev/null +++ b/problems/1023.camelcase-matching.md @@ -0,0 +1,136 @@ +## 题目地址 + +https://leetcode-cn.com/problems/camelcase-matching/ + +## 题目描述 + +``` +如果我们可以将小写字母插入模式串 pattern 得到待查询项 query,那么待查询项与给定模式串匹配。(我们可以在任何位置插入每个字符,也可以插入 0 个字符。) + +给定待查询列表 queries,和模式串 pattern,返回由布尔值组成的答案列表 answer。只有在待查项 queries[i] 与模式串 pattern 匹配时, answer[i] 才为 true,否则为 false。 + +  + +示例 1: + +输入:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FB" +输出:[true,false,true,true,false] +示例: +"FooBar" 可以这样生成:"F" + "oo" + "B" + "ar"。 +"FootBall" 可以这样生成:"F" + "oot" + "B" + "all". +"FrameBuffer" 可以这样生成:"F" + "rame" + "B" + "uffer". +示例 2: + +输入:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FoBa" +输出:[true,false,true,false,false] +解释: +"FooBar" 可以这样生成:"Fo" + "o" + "Ba" + "r". +"FootBall" 可以这样生成:"Fo" + "ot" + "Ba" + "ll". +示例 3: + +输出:queries = ["FooBar","FooBarTest","FootBall","FrameBuffer","ForceFeedBack"], pattern = "FoBaT" +输入:[false,true,false,false,false] +解释: +"FooBarTest" 可以这样生成:"Fo" + "o" + "Ba" + "r" + "T" + "est". +  + +提示: + +1 <= queries.length <= 100 +1 <= queries[i].length <= 100 +1 <= pattern.length <= 100 +所有字符串都仅由大写和小写英文字母组成。 + +``` + +## 思路 + +这道题是一道典型的双指针题目。不过这里的双指针并不是指向同一个数组或者字符串,而是指向多个,这道题是指向两个,分别是 query 和 pattern,这种题目非常常见,能够识别和掌握这种题目的解题模板非常重要。对 queries 的每一项我们的逻辑是一样的,这里就以其中一项为例进行讲解。 + +以 query 为 FooBar,pattern 为 FB 为例。 + +首先我们来简化一下问题,假如我们没有`可以在任何位置插入每个字符,也可以插入 0 个字符。`这个规则。我们的问题会比较简单,这个时候我们的算法是什么样的呢?一起来看下: + +1. 首先我们建立两个指针 i 和 j 分别指向 query 和 pattern 的首字母。 +2. 当 i 和 j 指向的字母相同的时候,我们同时向后移动两个指针一个单位。 +3. 当 i 和 j 指向的字母不同的时候,我们直接返回 False + +假如我们要找到的不是子串,而是子序列怎么办?我们不妨假设判断 pattern 是否是 query 的子序列。 其实 LeetCode 实际上也有这样的题目,我们来看下: + +1. 首先我们建立两个指针 i 和 j 分别指向 query 和 pattern 的首字母。 +2. 当 i 和 j 指向的字母相同的时候,我们同时向后移动两个指针一个单位。 +3. 当 i 和 j 指向的字母不同的时候,我们移动 i 指针。 +4. 当 i 超出 query 范围的时候,我们只需要判断 pattern 是否达到了终点即可。当然我们也可以提前退出。 + +我们直接参考下 LeetCode [392. 判断子序列](https://leetcode-cn.com/problems/is-subsequence/)。 + +代码: + +> 给定字符串 s 和 t ,判断 s 是否为 t 的子序列 + +Python Code: + +```python +class Solution: + def isSubsequence(self, s: str, t: str) -> bool: + i = 0 + j = 0 + while j < len(t): + if i < len(s) and s[i] == t[j]: + i += 1 + j += 1 + else: + j += 1 + if i >= len (s): + return True + return i == len(s) +``` + +然后我们加上`可以在任何位置插入每个字符,也可以插入 0 个字符。`这个规则。来看下有什么不同: + +1. 首先我们建立两个指针 i 和 j 分别指向 query 和 pattern 的首字母。 +2. 当 i 和 j 指向的字母相同的时候,我们同时向后移动两个指针一个单位。 +3. 当 i 和 j 指向的字母不同的时候,我们继续判断 i 指向的元素是否是小写。 +4. 如果是小写我们只把 i 向后移动一个单位。 +5. 如果不是小写我们直接返回 False + +## 关键点解析 + +- 双指针 +- 字符串匹配 +- 子序列 +- 子串 + +## 代码 + +Python Code: + +```python +class Solution: + def camelMatch(self, queries: List[str], pattern: str) -> List[bool]: + res = [] + for query in queries: + i = 0 + j = 0 + while i < len(query): + if j < len(pattern) and query[i] == pattern[j]: + i += 1 + j += 1 + elif query[i].islower(): + i += 1 + else: + break + if i == len(query) and j == len(pattern): + res.append(True) + else: + res.append(False) + return res +``` + +## 扩展 + +这是一个符合直觉的解法,但是却不是一个很优秀的解法,那么你有想到什么优秀的解法么? + +## 参考 + +- [392. 判断子序列](https://leetcode-cn.com/problems/is-subsequence/) diff --git a/problems/103.binary-tree-zigzag-level-order-traversal.md b/problems/103.binary-tree-zigzag-level-order-traversal.md new file mode 100644 index 0000000..7cf53ec --- /dev/null +++ b/problems/103.binary-tree-zigzag-level-order-traversal.md @@ -0,0 +1,213 @@ + +## 题目地址 +https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/description/ + +## 题目描述 +和leetcode 102 基本是一样的,思路是完全一样的。 + +``` +Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between). + +For example: +Given binary tree [3,9,20,null,null,15,7], + 3 + / \ + 9 20 + / \ + 15 7 +return its zigzag level order traversal as: +[ + [3], + [20,9], + [15,7] +] +``` + +## 思路 + +这道题可以借助`队列`实现,首先把root入队,然后入队一个特殊元素Null(来表示每层的结束)。 + + +然后就是while(queue.length), 每次处理一个节点,都将其子节点(在这里是left和right)放到队列中。 + +然后不断的出队, 如果出队的是null,则表式这一层已经结束了,我们就继续push一个null。 + + +## 关键点解析 + +- 队列 + +- 队列中用Null(一个特殊元素)来划分每层 + +- 树的基本操作- 遍历 - 层次遍历(BFS) + + +## 代码 + +* 语言支持:JS,C++ + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=103 lang=javascript + * + * [103] Binary Tree Zigzag Level Order Traversal + * + * https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/description/ + * + * algorithms + * Medium (40.57%) + * Total Accepted: 201.2K + * Total Submissions: 493.7K + * Testcase Example: '[3,9,20,null,null,15,7]' + * + * Given a binary tree, return the zigzag level order traversal of its nodes' + * values. (ie, from left to right, then right to left for the next level and + * alternate between). + * + * + * For example: + * Given binary tree [3,9,20,null,null,15,7], + * + * ⁠ 3 + * ⁠ / \ + * ⁠ 9 20 + * ⁠ / \ + * ⁠ 15 7 + * + * + * + * return its zigzag level order traversal as: + * + * [ + * ⁠ [3], + * ⁠ [20,9], + * ⁠ [15,7] + * ] + * + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[][]} + */ +var zigzagLevelOrder = function(root) { + if (!root) return []; + const items = []; + let isOdd = true; + let levelNodes = []; + + const queue = [root, null]; + + + while(queue.length > 0) { + const t = queue.shift(); + + if (t) { + levelNodes.push(t.val) + if (t.left) { + queue.push(t.left) + } + if (t.right) { + queue.push(t.right) + } + } else { + if (!isOdd) { + levelNodes = levelNodes.reverse(); + } + items.push(levelNodes) + levelNodes = []; + isOdd = !isOdd; + if (queue.length > 0) { + queue.push(null); + } + } + } + + return items + +}; +``` +C++ Code: +```C++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +public: + vector> zigzagLevelOrder(TreeNode* root) { + auto ret = vector>(); + if (root == nullptr) return ret; + auto queue = vector{root}; + auto isOdd = true; + while (!queue.empty()) { + auto sz = queue.size(); + auto level = vector(); + for (auto i = 0; i < sz; ++i) { + auto n = queue.front(); + queue.erase(queue.begin()); + if (isOdd) level.push_back(n->val); + else level.insert(level.begin(), n->val); + if (n->left != nullptr) queue.push_back(n->left); + if (n->right != nullptr) queue.push_back(n->right); + } + isOdd = !isOdd; + ret.push_back(level); + } + return ret; + } +}; +``` +## 拓展 + +由于二叉树是递归结构,因此,可以采用递归的方式来处理。在递归时需要保留当前的层次信息(从0开始),作为参数传递给下一次递归调用。 + +### 描述 + +1. 当前层次为偶数时,将当前节点放到当前层的结果数组尾部 +2. 当前层次为奇数时,将当前节点放到当前层的结果数组头部 +3. 递归对左子树进行之字形遍历,层数参数为当前层数+1 +4. 递归对右子树进行之字形遍历,层数参数为当前层数+1 + +### C++实现 + +```C++ +class Solution { +public: + vector> zigzagLevelOrder(TreeNode* root) { + auto ret = vector>(); + zigzagLevelOrder(root, 0, ret); + return ret; + } +private: + void zigzagLevelOrder(const TreeNode* root, int level, vector>& ret) { + if (root == nullptr || level < 0) return; + if (ret.size() <= level) { + ret.push_back(vector()); + } + if (level % 2 == 0) ret[level].push_back(root->val); + else ret[level].insert(ret[level].begin(), root->val); + zigzagLevelOrder(root->left, level + 1, ret); + zigzagLevelOrder(root->right, level + 1, ret); + } +}; +``` + +## 相关题目 +- [102.binary-tree-level-order-traversal](./102.binary-tree-level-order-traversal.md) +- [104.maximum-depth-of-binary-tree](./104.maximum-depth-of-binary-tree.md) + diff --git a/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.md b/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.md new file mode 100644 index 0000000..f376480 --- /dev/null +++ b/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.md @@ -0,0 +1,126 @@ +## 题目地址 +https://leetcode.com/problems/maximum-sum-of-two-non-overlapping-subarrays/ + +## 题目描述 + +``` +Given an array A of non-negative integers, return the maximum sum of elements in two non-overlapping (contiguous) subarrays, which have lengths L and M. (For clarification, the L-length subarray could occur before or after the M-length subarray.) + +Formally, return the largest V for which V = (A[i] + A[i+1] + ... + A[i+L-1]) + (A[j] + A[j+1] + ... + A[j+M-1]) and either: + +0 <= i < i + L - 1 < j < j + M - 1 < A.length, or +0 <= j < j + M - 1 < i < i + L - 1 < A.length. + + +Example 1: + +Input: A = [0,6,5,2,2,5,1,9,4], L = 1, M = 2 +Output: 20 +Explanation: One choice of subarrays is [9] with length 1, and [6,5] with length 2. +Example 2: + +Input: A = [3,8,1,3,2,1,8,9,0], L = 3, M = 2 +Output: 29 +Explanation: One choice of subarrays is [3,8,1] with length 3, and [8,9] with length 2. +Example 3: + +Input: A = [2,1,5,6,0,9,5,0,3,8], L = 4, M = 3 +Output: 31 +Explanation: One choice of subarrays is [5,6,0,9] with length 4, and [3,8] with length 3. + + +Note: + +L >= 1 +M >= 1 +L + M <= A.length <= 1000 +0 <= A[i] <= 1000 +``` + +## 思路(动态规划) + +题目中要求在前N(数组长度)个数中找出长度分别为L和M的非重叠子数组之和的最大值, 因此, 我们可以定义数组A中前i个数可构成的非重叠子数组L和M的最大值为SUMM[i], 并找到SUMM[i]和SUMM[i-1]的关系, 那么最终解就是SUMM[N]. 以下为图解: + +![1031.Maximum Sum of Two Non-Overlapping Subarrays](../assets/problems/1031.maximum-sum-of-two-non-overlapping-subarrays.png) + +## 关键点解析 + +1. 注意图中描述的都是A[i-1], 而不是A[i], 因为base case为空数组, 而不是A[0]; +2. 求解图中ASUM数组的时候, 注意定义的是ASUM[i] = sum(A[0:i]), 因此当i等于0时, A[0:0]为空数组, 即: ASUM[0]为0, 而ASUM[1]才等于A[0]; +3. 求解图中MAXL数组时, 注意i < L时, 没有意义, 因为长度不够, 所以从i = L时才开始求解; +4. 求解图中MAXM数组时, 也一样, 要从i = M时才开始求解; +5. 求解图中SUMM数组时, 因为我们需要一个L子数组和一个M子数组, 因此长度要大于等于L+M才有意义, 所以要从i = L + M时开始求解. + +## 代码 + +* 语言支持: Python + +Python Code: +```python +class Solution: + def maxSumTwoNoOverlap(self, a: List[int], l: int, m: int) -> int: + """ + + define asum[i] as the sum of subarray, a[0:i] + define maxl[i] as the maximum sum of l-length subarray in a[0:i] + define maxm[i] as the maximum sum of m-length subarray in a[0:i] + define msum[i] as the maximum sum of non-overlap l-length subarray and m-length subarray + + case 1: a[i] is both not in l-length subarray and m-length subarray, then msum[i] = msum[i - 1] + case 2: a[i] is in l-length subarray, then msum[i] = asum[i] - asum[i-l] + maxm[i-l] + case 3: a[i] is in m-length subarray, then msum[i] = asum[i] - asum[i-m] + maxl[i-m] + + so, msum[i] = max(msum[i - 1], asum[i] - asum[i-l] + maxl[i-l], asum[i] - asum[i-m] + maxm[i-m]) + """ + + alen, tlen = len(a), l + m + asum = [0] * (alen + 1) + maxl = [0] * (alen + 1) + maxm = [0] * (alen + 1) + msum = [0] * (alen + 1) + + for i in range(tlen): + if i == 1: + asum[i] = a[i - 1] + elif i > 1: + asum[i] = asum[i - 1] + a[i - 1] + if i >= l: + maxl[i] = max(maxl[i - 1], asum[i] - asum[i - l]) + if i >= m: + maxm[i] = max(maxm[i - 1], asum[i] - asum[i - m]) + + for i in range(tlen, alen + 1): + asum[i] = asum[i - 1] + a[i - 1] + suml = asum[i] - asum[i - l] + summ = asum[i] - asum[i - m] + maxl[i] = max(maxl[i - 1], suml) + maxm[i] = max(maxm[i - 1], summ) + msum[i] = max(msum[i - 1], suml + maxm[i - l], summ + maxl[i - m]) + + return msum[-1] +``` + +## 扩展 + +1. 代码中, 求解了4个动态规划数组来求解最终值, 有没有可能只用两个数组来求解该题, 可以的话, 需要保留的又是哪两个数组? +2. 代码中, 求解的4动态规划数组的顺序能否改变, 哪些能改, 哪些不能改? + +如果采用前缀和数组的话,可以只使用O(n)的空间来存储前缀和,O(1)的动态规划状态空间来完成。C++代码如下: +```C++ +class Solution { +public: + int maxSumTwoNoOverlap(vector& A, int L, int M) { + auto tmp = vector{A[0]}; + for (auto i = 1; i < A.size(); ++i) { + tmp.push_back(A[i] + tmp[i - 1]); + } + auto res = tmp[L + M - 1], lMax = tmp[L - 1], mMax = tmp[M - 1]; + for (auto i = L + M; i < tmp.size(); ++i) { + lMax = max(lMax, tmp[i - M] - tmp[i - M - L]); + mMax = max(mMax, tmp[i - L] - tmp[i - L - M]); + res = max(res, max(lMax + tmp[i] - tmp[i - M], mMax + tmp[i] - tmp[i - L])); + } + return res; + } +}; +``` diff --git a/problems/104.maximum-depth-of-binary-tree.md b/problems/104.maximum-depth-of-binary-tree.md new file mode 100644 index 0000000..a526541 --- /dev/null +++ b/problems/104.maximum-depth-of-binary-tree.md @@ -0,0 +1,152 @@ +## 题目地址 + +https://leetcode.com/problems/maximum-depth-of-binary-tree/description/ + +## 题目描述 + +``` +Given a binary tree, find its maximum depth. + +The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node. + +Note: A leaf is a node with no children. + +Example: + +Given binary tree [3,9,20,null,null,15,7], + + 3 + / \ + 9 20 + / \ + 15 7 +return its depth = 3. + +``` + +## 思路 + +由于树是一种递归的数据结构,因此用递归去解决的时候往往非常容易,这道题恰巧也是如此, +用递归实现的代码如下: + +```js +var maxDepth = function(root) { + if (!root) return 0; + if (!root.left && !root.right) return 1; + return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); +}; +``` + +如果使用迭代呢? 我们首先应该想到的是树的各种遍历,由于我们求的是深度,因此 +使用层次遍历(BFS)是非常合适的。 我们只需要记录有多少层即可。相关思路请查看[binary-tree-traversal](../thinkings/binary-tree-traversal.md) + +## 关键点解析 + +- 队列 + +- 队列中用 Null(一个特殊元素)来划分每层,或者在对每层进行迭代之前保存当前队列元素的个数(即当前层所含元素个数) + +- 树的基本操作- 遍历 - 层次遍历(BFS) + +## 代码 +* 语言支持:JS,C++,Python + +JavaScript Code: +```js +/* + * @lc app=leetcode id=104 lang=javascript + * + * [104] Maximum Depth of Binary Tree + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number} + */ +var maxDepth = function(root) { + if (!root) return 0; + if (!root.left && !root.right) return 1; + + // 层次遍历 BFS + let cur = root; + const queue = [root, null]; + let depth = 1; + + while ((cur = queue.shift()) !== undefined) { + if (cur === null) { + // 注意⚠️: 不处理会无限循环,进而堆栈溢出 + if (queue.length === 0) return depth; + depth++; + queue.push(null); + continue; + } + const l = cur.left; + const r = cur.right; + + if (l) queue.push(l); + if (r) queue.push(r); + } + + return depth; +}; +``` +C++ Code: +```C++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +public: + int maxDepth(TreeNode* root) { + if (root == nullptr) return 0; + auto q = vector(); + auto d = 0; + q.push_back(root); + while (!q.empty()) + { + ++d; + auto sz = q.size(); + for (auto i = 0; i < sz; ++i) + { + auto t = q.front(); + q.erase(q.begin()); + if (t->left != nullptr) q.push_back(t->left); + if (t->right != nullptr) q.push_back(t->right); + } + } + return d; + } +}; +``` + +Python Code: +```python +class Solution: + def maxDepth(self, root: TreeNode) -> int: + if not root: return 0 + q, depth = [root, None], 1 + while q: + node = q.pop(0) + if node: + if node.left: q.append(node.left) + if node.right: q.append(node.right) + elif q: + q.append(None) + depth += 1 + return depth +``` +## 相关题目 +- [102.binary-tree-level-order-traversal](./102.binary-tree-level-order-traversal.md) +- [103.binary-tree-zigzag-level-order-traversal](./103.binary-tree-zigzag-level-order-traversal.md) diff --git a/problems/105.Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.md b/problems/105.Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.md new file mode 100644 index 0000000..24c8091 --- /dev/null +++ b/problems/105.Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.md @@ -0,0 +1,113 @@ +## 问题地址/Problem URL + +https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ + +## 问题介绍/Problem Description + +Given preorder and inorder traversal of a tree, construct the binary tree. + +Note: +You may assume that duplicates do not exist in the tree. + +For example, given + +```java +preorder = [3,9,20,15,7] +inorder = [9,3,15,20,7] +``` + +Return the following binary tree: + +```bash + 3 + / \ + 9 20 + / \ + 15 7 +``` + +## 思路/Thinking Path + +目标是构造二叉树。 + +构造二叉树需要根的值、左子树和右子树。 + +此问题可被抽象为:从前序遍历和中序遍历中找到根节点、左子树和右子树。 + +先找根: +由前序遍历的性质,第`0`个节点为当前树的根节点。 +再找左右子树: +在中序遍历中找到这个根节点,设其下标为`i`。由中序遍历的性质,`0 ~ i-1` 是左子树的中序遍历,`i+1 ~ inorder.length-1`是右子树的中序遍历。 + +然后递归求解,终止条件是左右子树为`null`。 + +We are going to construct a binary tree from its preorder and inorder traversal. + +To build a binary tree, it requires us to creact a new `TreeNode` as the root with filling in the root value. And then, find its left child and right child recursively until the left or right child is `null`. + +Now this problem is abstracted as how to find the root node, left child and right child from the preorder traversal and inorder traversal. + +In preorder traversal, the first node (`preorder[0]`) is the root of current binary tree. In inorder traversal, find the location of this root which is `i`. The left sub-tree is `0 to i-1` and the right sub-tree is `i+1 to inorder.length-1` in inorder traversal. + +Then applying the previous operation to the left and right sub-trees. + +## 关键解析/Key Points + +如何在前序遍历的数组里找到左右子树的: +- 根据前序遍历的定义可知,每个当前数组的第一个元素就是当前子树的根节点的值 +- 在中序遍历数组中找到这个值,设其下标为`inRoot` + - 当前中序遍历数组的起点`inStart`到`inRoot`之间就是左子树,其长度`leftChldLen`为`inRoot-inStart` + - 当前中序遍历数组的终点`inEnd`和`inRoot`之间就是右子树 +- 前序遍历和中序遍历中左子树的长度是相等的,所以在前序遍历数组中,根节点下标`preStart`往后数`leftChldLen`即为左子树的最后一个节点,其下标为`preStart+leftChldLen`,右子树的第一个节点下标为`preStart+leftChldLen+1`。 + +**PLEASE READ THE CODE BEFORE READING THIS PART** + +If you can't figure out how to get the index of the left and right child, please read this. + +- index of current node in preorder array is preStart(or whatever your call it), it's the root of a subtree. +- according to the properties of preoder traversal, all right sub-tree nodes are behine all left sub-tree nodes. The length of left sub-tree can help us to divide left and right sub-trees. +- the length of left sub-tree can be find in the inorder traversal. The location of current node is `inRoot`(or whatever your call it). The start index of current inorder array is `inStart`(or whatever your call it). So, the lenght of the left sub-tree is `leftChldLen = inRoot - inStart`. + +![explain](../assets/problems/105.index_explain.jpg) + +## 代码/Code + +- Java + +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +class Solution { + public TreeNode buildTree(int[] preorder, int[] inorder) { + if (preorder.length != inorder.length) return null; + + HashMap map = new HashMap<> (); + + for (int i=0; i map) { + if (preStart>preEnd || inStart>inEnd) return null; + + TreeNode root = new TreeNode(preorder[prestart]); + int inRoot = map.get(preorder[preStart]); + int leftChldLen = inRoot - inStart; + + root.left = helper(preorder, preStart+1, preStart+leftChldLen, inorder, inStart, inRoot-1, map); + root.left = helper(preorder, preStart+leftChldLen+1, preEnd, inorder, inRoot+1, inEnd, map); + + return root; + } +} +``` \ No newline at end of file diff --git a/problems/11.container-with-most-water.md b/problems/11.container-with-most-water.md new file mode 100644 index 0000000..b6b5de3 --- /dev/null +++ b/problems/11.container-with-most-water.md @@ -0,0 +1,125 @@ +## 题目地址 +https://leetcode.com/problems/container-with-most-water/description/ + +## 题目描述 +``` +Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water. + +Note: You may not slant the container and n is at least 2. +``` + +![11.container-with-most-water-question](../assets/problems/11.container-with-most-water-question.jpg) +``` + +The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49. + + + +Example: + +Input: [1,8,6,2,5,4,8,3,7] +Output: 49 +``` + +## 思路 +符合直觉的解法是,我们可以对两两进行求解,计算可以承载的水量。 然后不断更新最大值,最后返回最大值即可。 +这种解法,需要两层循环,时间复杂度是O(n^2) + +eg: + +```js + // 这个解法比较暴力,效率比较低 + // 时间复杂度是O(n^2) + let max = 0; + for(let i = 0; i < height.length; i++) { + for(let j = i + 1; j < height.length; j++) { + const currentArea = Math.abs(i - j) * Math.min(height[i], height[j]); + if (currentArea > max) { + max = currentArea; + } + } + } + return max; + +``` + +> 这种符合直觉的解法有点像冒泡排序, 大家可以稍微类比一下 + +那么有没有更加优的解法呢?我们来换个角度来思考这个问题,上述的解法是通过两两组合,这无疑是完备的, +那我门是否可以先计算长度为n的面积,然后计算长度为n-1的面积,... 计算长度为1的面积。 这样去不断更新最大值呢? +很显然这种解法也是完备的,但是似乎时间复杂度还是O(n ^ 2), 不要着急。 + +考虑一下,如果我们计算n-1长度的面积的时候,是直接直接排除一半的结果的。 + +如图: + +![11.container-with-most-water](../assets/problems/11.container-with-most-water.png) + + +比如我们计算n面积的时候,假如左侧的线段高度比右侧的高度低,那么我们通过左移右指针来将长度缩短为n-1的做法是没有意义的, +因为`新的形成的面积变成了(n-1) * heightOfLeft 这个面积一定比刚才的长度为n的面积nn * heightOfLeft 小` + +也就是说最大面积`一定是当前的面积或者通过移动短的线段得到`。 +## 关键点解析 + +- 双指针优化时间复杂度 + + +## 代码 +* 语言支持:JS,C++ + +JavaScript Code: + +```js +/** + * @param {number[]} height + * @return {number} + */ +var maxArea = function(height) { + if (!height || height.length <= 1) return 0; + + let leftPos = 0; + let rightPos = height.length - 1; + let max = 0; + while(leftPos < rightPos) { + + const currentArea = Math.abs(leftPos - rightPos) * Math.min(height[leftPos] , height[rightPos]); + if (currentArea > max) { + max = currentArea; + } + // 更新小的 + if (height[leftPos] < height[rightPos]) { + leftPos++; + } else { // 如果相等就随便了 + rightPos--; + } + } + + return max; +}; +``` +C++ Code: +```C++ +class Solution { +public: + int maxArea(vector& height) { + auto ret = 0ul, leftPos = 0ul, rightPos = height.size() - 1; + while( leftPos < rightPos) + { + ret = std::max(ret, std::min(height[leftPos], height[rightPos]) * (rightPos - leftPos)); + if (height[leftPos] < height[rightPos]) ++leftPos; + else --rightPos; + } + return ret; + } +}; +``` + +***复杂度分析*** +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(1)$ + + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) diff --git a/problems/1104.path-in-zigzag-labelled-binary-tree.md b/problems/1104.path-in-zigzag-labelled-binary-tree.md new file mode 100644 index 0000000..2355775 --- /dev/null +++ b/problems/1104.path-in-zigzag-labelled-binary-tree.md @@ -0,0 +1,80 @@ +## 题目地址 + +https://leetcode-cn.com/problems/path-in-zigzag-labelled-binary-tree/description/ + +## 题目描述 + +在一棵无限的二叉树上,每个节点都有两个子节点,树中的节点 逐行 依次按 “之” 字形进行标记。 + +如下图所示,在奇数行(即,第一行、第三行、第五行……)中,按从左到右的顺序进行标记; + +而偶数行(即,第二行、第四行、第六行……)中,按从右到左的顺序进行标记。 + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gaihhb1ysbj310p0gu3zx.jpg) + +给你树上某一个节点的标号 label,请你返回从根节点到该标号为 label 节点的路径,该路径是由途经的节点标号所组成的。 + +示例 1: + +输入:label = 14 +输出:[1,3,4,14] +示例 2: + +输入:label = 26 +输出:[1,2,6,10,26] + +提示: + +1 <= label <= 10^6 + +## 思路 + +假如这道题不是之字形,那么就会非常简单。 我们可以根据子节点的 label 轻松地求出父节点的 label,公示是 label // 2(其中 label 为子节点的 label)。 + +如果是这样的话,这道题应该是 easy 难度,代码也不难写出。我们继续考虑之字形。我们不妨先观察一下,找下规律。 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gaihn0ktanj30lu093gme.jpg) + +以上图最后一行为例,对于 15 节点,之字变换之前对应的应该是 8 节点。14 节点对应的是 9 节点。。。 + +全部列举出来是这样的: + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gaihota82cj30mk0b6wfp.jpg) + +我们发现之字变换前后的 label 相加是一个定值。 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gaihpnlpicj309b08dmxl.jpg) + +因此我们只需要求解出每一层的这个定值,然后减去当前值就好了。(注意我们不需要区分偶数行和奇数行) +问题的关键转化为求解这个定值,这个定值其实很好求,因为每一层的最大值和最小值我们很容易求,而最大值和最小值的和正是我们要求的这个数字。 + +最大值和最小值这么求呢?由满二叉树的性质,我们知道每一层的最小值就是`2 ** (level - 1)`,而最大值是`2 ** level - 1`。 因此我们只要知道 level 即可,level 非常容易求出,具体可以看下面代码。 + +## 关键点 + +- 满二叉树的性质: + +1. 最小值是`2 ** (level - 1)`,最大值是`2 ** level - 1`,其中 level 是树的深度。 +2. 假如父节点的索引为 i,那么左子节点就是 2\*i, 右边子节点就是 2\*i + 1。 +3. 假如子节点的索引是 i,那么父节点的索引就是 i // 2。 + +- 先思考一般情况(不是之字形), 然后通过观察找出规律 + +## 代码 + +```python +class Solution: + def pathInZigZagTree(self, label: int) -> List[int]: + level = 0 + res = [] + # for each level, ranged from 2 ** (level - 1) to 2 ** level - 1 + while 2 ** level - 1 < label: + level += 1 + + while level > 0: + res.insert(0, label) + label = 2 ** (level - 1) + 2 ** level - 1 - label + label //= 2 + level -= 1 + return res +``` diff --git a/problems/113.path-sum-ii.md b/problems/113.path-sum-ii.md new file mode 100644 index 0000000..e0e3d09 --- /dev/null +++ b/problems/113.path-sum-ii.md @@ -0,0 +1,166 @@ +## 题目地址 +https://leetcode.com/problems/path-sum-ii/description/ + +## 题目描述 +``` +Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum. + +Note: A leaf is a node with no children. + +Example: + +Given the below binary tree and sum = 22, + + 5 + / \ + 4 8 + / / \ + 11 13 4 + / \ / \ +7 2 5 1 +Return: + +[ + [5,4,11,2], + [5,8,4,5] +] +``` + +## 思路 + +这道题目是求集合,并不是`求值`,而是枚举所有可能,因此动态规划不是特别切合,因此我们需要考虑别的方法。 + +这种题目其实有一个通用的解法,就是回溯法。 +网上也有大神给出了这种回溯法解题的 +[通用写法](https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)),这里的所有的解法使用通用方法解答。 +除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。 + +我们先来看下通用解法的解题思路,我画了一张图: + +![backtrack](../assets/problems/backtrack.png) + +通用写法的具体代码见下方代码区。 + +## 关键点解析 + +- 回溯法 +- backtrack 解题公式 + + +## 代码 + +* 语言支持:JS,C++,Python3 + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=113 lang=javascript + * + * [113] Path Sum II + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function backtrack(root, sum, res, tempList) { + if (root === null) return; + if (root.left === null && root.right === null && sum === root.val) + return res.push([...tempList, root.val]); + + tempList.push(root.val); + backtrack(root.left, sum - root.val, res, tempList); + + backtrack(root.right, sum - root.val, res, tempList); + tempList.pop(); +} +/** + * @param {TreeNode} root + * @param {number} sum + * @return {number[][]} + */ +var pathSum = function(root, sum) { + if (root === null) return []; + const res = []; + backtrack(root, sum, res, []); + return res; +}; +``` +C++ Code: +```C++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +public: + vector> pathSum(TreeNode* root, int sum) { + auto ret = vector>(); + auto temp = vector(); + backtrack(root, sum, ret, temp); + return ret; + } +private: + void backtrack(const TreeNode* root, int sum, vector>& ret, vector& tempList) { + if (root == nullptr) return; + tempList.push_back(root->val); + if (root->val == sum && root->left == nullptr && root->right == nullptr) { + ret.push_back(tempList); + } else { + backtrack(root->left, sum - root->val, ret, tempList); + backtrack(root->right, sum - root->val, ret, tempList); + } + tempList.pop_back(); + } +}; +``` +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]: + if not root: + return [] + + result = [] + + def trace_node(pre_list, left_sum, node): + new_list = pre_list.copy() + new_list.append(node.val) + if not node.left and not node.right: + # 这个判断可以和上面的合并,但分开写会快几毫秒,可以省去一些不必要的判断 + if left_sum == node.val: + result.append(new_list) + else: + if node.left: + trace_node(new_list, left_sum-node.val, node.left) + if node.right: + trace_node(new_list, left_sum-node.val, node.right) + + trace_node([], sum, root) + return result +``` +## 相关题目 + +- [39.combination-sum](./39.combination-sum.md) +- [40.combination-sum-ii](./40.combination-sum-ii.md) +- [46.permutations](./46.permutations.md) +- [47.permutations-ii](./47.permutations-ii.md) +- [78.subsets](./78.subsets.md) +- [90.subsets-ii](./90.subsets-ii.md) +- [131.palindrome-partitioning](./131.palindrome-partitioning.md) + + diff --git a/problems/1131.maximum-of-absolute-value-expression.md b/problems/1131.maximum-of-absolute-value-expression.md new file mode 100644 index 0000000..763af24 --- /dev/null +++ b/problems/1131.maximum-of-absolute-value-expression.md @@ -0,0 +1,187 @@ +## 题目地址(1131. 绝对值表达式的最大值) + +https://leetcode-cn.com/problems/maximum-of-absolute-value-expression/description/ + +## 题目描述 + +给你两个长度相等的整数数组,返回下面表达式的最大值: + +|arr1[i] - arr1[j]| + |arr2[i] - arr2[j]| + |i - j| + +其中下标 i,j 满足 0 <= i, j < arr1.length。 + +示例 1: + +输入:arr1 = [1,2,3,4], arr2 = [-1,4,5,6] +输出:13 +示例 2: + +输入:arr1 = [1,-2,-5,0,10], arr2 = [0,-2,-1,-7,-4] +输出:20 + +提示: + +2 <= arr1.length == arr2.length <= 40000 +-10^6 <= arr1[i], arr2[i] <= 10^6 + +## 解法一(数学分析) + +### 思路 + +如图我们要求的是这样一个表达式的最大值。arr1 和 arr2 为两个不同的数组,且二者长度相同。i 和 j 是两个合法的索引。 + +> 红色竖线表示的是绝对值的符号 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gamo3dx1bej30q003y74f.jpg) + +我们对其进行分类讨论,有如下八种情况: + +> |arr1[i] -arr1[j]| 两种情况 +> |arr2[i] -arr2[j]| 两种情况 +> |i - j| 两种情况 +> 因此一共是 2 \* 2 \* 2 = 8 种 + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gamosnsknej30tg0viq6w.jpg) + +由于 i 和 j 之前没有大小关系,也就说二者可以相互替代。因此: + +- 1 等价于 8 +- 2 等价于 7 +- 3 等价于 6 +- 4 等价于 5 + +也就是说我们只需要计算 1,2,3,4 的最大值就可以了。(当然你可以选择其他组合,只要完备就行) + +为了方便,我们将 i 和 j 都提取到一起: + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gamp5sizefj30qs0g6gmx.jpg) + +容易看出等式的最大值就是前面的最大值,和后面最小值的差值。如图: + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gamp9c3g9lj30r20kcabx.jpg) + +再仔细观察,会发现前面部分和后面部分是一样的,原因还是上面所说的 i 和 j 可以互换。因此我们要做的就是: + +- 遍历一遍数组,然后计算四个表达式, arr1[i] + arr2[i] + i,arr1[i] - arr2[i] + i,arr2[i] - arr1[i] + i 和 -1 \* arr2[i] - arr1[i] + i 的 最大值和最小值。 +- 然后分别取出四个表达式最大值和最小值的差值(就是这个表达式的最大值) +- 四个表达式最大值再取出最大值 + +### 关键点 + +- 数学分析 + +### 代码 + +```python +class Solution: + def maxAbsValExpr(self, arr1: List[int], arr2: List[int]) -> int: + A = [] + B = [] + C = [] + D = [] + for i in range(len(arr1)): + a, b, c, d = arr1[i] + arr2[i] + i, arr1[i] - arr2[i] + \ + i, arr2[i] - arr1[i] + i, -1 * arr2[i] - arr1[i] + i + A.append(a) + B.append(b) + C.append(c) + D.append(d) + return max(max(A) - min(A), max(B) - min(B), max(C) - min(C), max(D) - min(D)) +``` + +## 解法二(曼哈顿距离) + +### 思路 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gampnn032tj308l0a8mxl.jpg) + +(图来自: https://zh.wikipedia.org/wiki/%E6%9B%BC%E5%93%88%E9%A0%93%E8%B7%9D%E9%9B%A2) + +一维曼哈顿距离可以理解为一条线上两点之间的距离: |x1 - x2|,其值为 max(x1 - x2, x2 - x1) + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gampr362oaj30l004mdfv.jpg) + +在平面上,坐标(x1, y1)的点 P1 与坐标(x2, y2)的点 P2 的曼哈顿距离为:|x1-x2| + |y1 - y2|,其值为 max(x1 - x2 + y1 - y2, x2 - x1 + y1 - y2, x1 - x2 + y2 - y1, x2 -x1 + y2 - y1) + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gampwhua9fj30rq0lmdgl.jpg) + +然后这道题目是更复杂的三维曼哈顿距离,其中(i, arr[i], arr[j])可以看作三位空间中的一个点,问题转化为曼哈顿距离最远的两个点的距离。 +延续上面的思路,|x1-x2| + |y1 - y2| + |z1 - z2|,其值为 : + +max( + +x1 - x2 + y1 - y2 + z1 - z2, + +x1 - x2 + y1 - y2 + z2 - z1, + +x2 - x1 + y1 - y2 + z1 - z2, + +x2 - x1 + y1 - y2 + z2 - z1, + +x1 - x2 + y2 - y1 + z1 - z2, + +x1 - x2 + y2 - y1 + z2- z1, + +x2 -x1 + y2 - y1 + z1 - z2, + +x2 -x1 + y2 - y1 + z2 - z1 + +) + +我们可以将 1 和 2 放在一起方便计算: + +max( + +x1 + y1 + z1 - (x2 + y2 + z2), + +x1 + y1 - z1 - (x2 + y2 - z2) + +... + +) + +我们甚至可以扩展到 n 维,具体代码见下方。 + +### 关键点 + +- 曼哈顿距离 +- 曼哈顿距离代码模板 + +> 解题模板可以帮助你快速并且更少错误的解题,更多解题模板请期待我的[新书](https://lucifer.ren/blog/2019/12/11/draft/)(未完成) + +### 代码 + +```python +class Solution: + def maxAbsValExpr(self, arr1: List[int], arr2: List[int]) -> int: + # 曼哈顿距离模板代码 + sign = [1, -1] + n = len(arr1) + dists = [] + # 三维模板 + for a in sign: + for b in sign: + for c in sign: + maxDist = float('-inf') + minDist = float('inf') + # 分别计算所有点的曼哈顿距离 + for i in range(n): + dist = arr1[i] * a + arr2[i] * b + i * c + maxDist = max(maxDist, dist) + minDist = min(minDist, dist) + # 将所有的点的曼哈顿距离放到dists中 + dists.append(maxDist - minDist) + return max(dists) +``` + +## 总结 + +可以看出其实两种解法都是一样的,只是思考角度不一样。 + +## 相关题目 + +- [1030. 距离顺序排列矩阵单元格](https://leetcode-cn.com/problems/matrix-cells-in-distance-order/) + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gamq577lgsj30xd0jzwjb.jpg) + +- [1162. 地图分析](https://leetcode-cn.com/problems/as-far-from-land-as-possible/) diff --git a/problems/1168.optimize-water-distribution-in-a-village-cn.md b/problems/1168.optimize-water-distribution-in-a-village-cn.md new file mode 100644 index 0000000..f0398a6 --- /dev/null +++ b/problems/1168.optimize-water-distribution-in-a-village-cn.md @@ -0,0 +1,187 @@ +## 题目地址 +https://leetcode.com/problems/optimize-water-distribution-in-a-village/ + +## 题目描述 +``` +There are n houses in a village. We want to supply water for all the houses by building wells and laying pipes. + +For each house i, we can either build a well inside it directly with cost wells[i], or pipe in water from another well to it. The costs to lay pipes between houses are given by the array pipes, where each pipes[i] = [house1, house2, cost] represents the cost to connect house1 and house2 together using a pipe. Connections are bidirectional. + +Find the minimum total cost to supply water to all houses. + +Example 1: + +Input: n = 3, wells = [1,2,2], pipes = [[1,2,1],[2,3,1]] +Output: 3 +Explanation: +The image shows the costs of connecting houses using pipes. +The best strategy is to build a well in the first house with cost 1 and connect the other houses to it with cost 2 so the total cost is 3. + +Constraints: + +1 <= n <= 10000 +wells.length == n +0 <= wells[i] <= 10^5 +1 <= pipes.length <= 10000 +1 <= pipes[i][0], pipes[i][1] <= n +0 <= pipes[i][2] <= 10^5 +pipes[i][0] != pipes[i][1] +``` +example 1 pic: + +![example 1](../assets/problems/1168.optimize-water-distribution-in-a-village-example1.png) + +## 思路 +题意,在每个城市打井需要一定的花费,也可以用其他城市的井水,城市之间建立连接管道需要一定的花费,怎么样安排可以花费最少的前灌溉所有城市。 + +这是一道连通所有点的最短路径/最小生成树问题,把城市看成图中的点,管道连接城市看成是连接两个点之间的边。这里打井的花费是直接在点上,而且并不是所有 +点之间都有边连接,为了方便,我们可以假想一个点`(root)0`,这里自身点的花费可以与 `0` 连接,花费可以是 `0-i` 之间的花费。这样我们就可以构建一个连通图包含所有的点和边。 +那在一个连通图中求最短路径/最小生成树的问题. + +参考延伸阅读中,维基百科针对这类题给出的几种解法。 + +解题步骤: +1. 创建 `POJO EdgeCost(node1, node2, cost) - 节点1 和 节点2 连接边的花费`。 +2. 假想一个`root` 点 `0`,构建图 +3. 连通所有节点和 `0`,`[0,i] - i 是节点 [1,n]`,`0-1` 是节点 `0` 和 `1` 的边,边的值是节点 `i` 上打井的花费 `wells[i]`; +4. 把打井花费和城市连接点转换成图的节点和边。 +5. 对图的边的值排序(从小到大) +6. 遍历图的边,判断两个节点有没有连通 (`Union-Find`), + - 已连通就跳过,继续访问下一条边 + - 没有连通,记录花费,连通节点 +7. 若所有节点已连通,求得的最小路径即为最小花费,返回 +8. 对于每次`union`, 节点数 `n-1`, 如果 `n==0` 说明所有节点都已连通,可以提前退出,不需要继续访问剩余的边。 + +> 这里用加权Union-Find 判断两个节点是否连通,和连通未连通的节点。 + +举例:`n = 5, wells=[1,2,2,3,2], pipes=[[1,2,1],[2,3,1],[4,5,7]]` + +如图: + +![minimum cost](../assets/problems/1168.optimize-water-distribution-in-a-village-1.png) + +从图中可以看到,最后所有的节点都是连通的。 + +#### 复杂度分析 +- *时间复杂度:* `O(ElogE) - E 是图的边的个数` +- *空间复杂度:* `O(E)` + +> 一个图最多有 `n(n-1)/2 - n 是图中节点个数` 条边 (完全连通图) + +## 关键点分析 +1. 构建图,得出所有边 +2. 对所有边排序 +3. 遍历所有的边(从小到大) +4. 对于每条边,检查是否已经连通,若没有连通,加上边上的值,连通两个节点。若已连通,跳过。 + +## 代码 (`Java/Python3`) +*Java code* +```java + class OptimizeWaterDistribution { + public int minCostToSupplyWater(int n, int[] wells, int[][] pipes) { + List costs = new ArrayList<>(); + for (int i = 1; i <= n; i++) { + costs.add(new EdgeCost(0, i, wells[i - 1])); + } + for (int[] p : pipes) { + costs.add(new EdgeCost(p[0], p[1], p[2])); + } + Collections.sort(costs); + int minCosts = 0; + UnionFind uf = new UnionFind(n); + for (EdgeCost edge : costs) { + int rootX = uf.find(edge.node1); + int rootY = uf.find(edge.node2); + if (rootX == rootY) continue; + minCosts += edge.cost; + uf.union(edge.node1, edge.node2); + // for each union, we connnect one node + n--; + // if all nodes already connected, terminate early + if (n == 0) { + return minCosts; + } + } + return minCosts; + } + + class EdgeCost implements Comparable { + int node1; + int node2; + int cost; + public EdgeCost(int node1, int node2, int cost) { + this.node1 = node1; + this.node2 = node2; + this.cost = cost; + } + + @Override + public int compareTo(EdgeCost o) { + return this.cost - o.cost; + } + } + + class UnionFind { + int[] parent; + int[] rank; + public UnionFind(int n) { + parent = new int[n + 1]; + for (int i = 0; i <= n; i++) { + parent[i] = i; + } + rank = new int[n + 1]; + } + public int find(int x) { + return x == parent[x] ? x : find(parent[x]); + } + public void union(int x, int y) { + int px = find(x); + int py = find(y); + if (px == py) return; + if (rank[px] >= rank[py]) { + parent[py] = px; + rank[px] += rank[py]; + } else { + parent[px] = py; + rank[py] += rank[px]; + } + } + } + } +``` +*Pythong3 code* +```python +class Solution: + def minCostToSupplyWater(self, n: int, wells: List[int], pipes: List[List[int]]) -> int: + union_find = {i: i for i in range(n + 1)} + + def find(x): + return x if x == union_find[x] else find(union_find[x]) + + def union(x, y): + px = find(x) + py = find(y) + union_find[px] = py + + graph_wells = [[cost, 0, i] for i, cost in enumerate(wells, 1)] + graph_pipes = [[cost, i, j] for i, j, cost in pipes] + min_costs = 0 + for cost, x, y in sorted(graph_wells + graph_pipes): + if find(x) == find(y): + continue + union(x, y) + min_costs += cost + n -= 1 + if n == 0: + return min_costs +``` + +## 延伸阅读 + +1. [最短路径问题](https://www.wikiwand.com/zh-hans/%E6%9C%80%E7%9F%AD%E8%B7%AF%E9%97%AE%E9%A2%98) +2. [Dijkstra算法](https://www.wikiwand.com/zh-hans/戴克斯特拉算法) +3. [Floyd-Warshall算法](https://www.wikiwand.com/zh-hans/Floyd-Warshall%E7%AE%97%E6%B3%95) +4. [Bellman-Ford算法](https://www.wikiwand.com/zh-hans/%E8%B4%9D%E5%B0%94%E6%9B%BC-%E7%A6%8F%E7%89%B9%E7%AE%97%E6%B3%95) +5. [Kruskal算法](https://www.wikiwand.com/zh-hans/%E5%85%8B%E9%B2%81%E6%96%AF%E5%85%8B%E5%B0%94%E6%BC%94%E7%AE%97%E6%B3%95) +6. [Prim's 算法](https://www.wikiwand.com/zh-hans/%E6%99%AE%E6%9E%97%E5%A7%86%E7%AE%97%E6%B3%95) +7. [最小生成树](https://www.wikiwand.com/zh/%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91) \ No newline at end of file diff --git a/problems/1168.optimize-water-distribution-in-a-village-en.md b/problems/1168.optimize-water-distribution-in-a-village-en.md new file mode 100644 index 0000000..3eac1ad --- /dev/null +++ b/problems/1168.optimize-water-distribution-in-a-village-en.md @@ -0,0 +1,194 @@ +## Problem + + + + +https://leetcode.com/problems/optimize-water-distribution-in-a-village/ +## Problem Description + +## 题目描述 +``` +There are n houses in a village. We want to supply water for all the houses by building wells and laying pipes. + +For each house i, we can either build a well inside it directly with cost wells[i], or pipe in water from another well to it. The costs to lay pipes between houses are given by the array pipes, where each pipes[i] = [house1, house2, cost] represents the cost to connect house1 and house2 together using a pipe. Connections are bidirectional. + +Find the minimum total cost to supply water to all houses. + +Example 1: + +Input: n = 3, wells = [1,2,2], pipes = [[1,2,1],[2,3,1]] +Output: 3 +Explanation: +The image shows the costs of connecting houses using pipes. +The best strategy is to build a well in the first house with cost 1 and connect the other houses to it with cost 2 so the total cost is 3. + +Constraints: + +1 <= n <= 10000 +wells.length == n +0 <= wells[i] <= 10^5 +1 <= pipes.length <= 10000 +1 <= pipes[i][0], pipes[i][1] <= n +0 <= pipes[i][2] <= 10^5 +pipes[i][0] != pipes[i][1] +``` +example 1 pic: + +![example 1](../assets/problems/1168.optimize-water-distribution-in-a-village-example1.png) + + +## Solution + +From example graph, we can see that this is Shortest path problem/Minimum spanning tree problem. In this problem, in a graph, view cities as nodes, pipe connects two cities as edges with cost. +here, wells costs, it is self connected edge, we can add extra node as root node `0`, and connect all `0` and `i` with costs `wells[i]`. So that we can have one graph/tree, +and how to get minimun spanning trees / shortest path problem in a graph. Please see below detailed steps for analysis. + +Analysis Steps: +1. Create `POJO EdgeCost(node1, node2, cost) - node1, node2, and cost of connect node1 and node2` +2. Assume on `root node 0`,build graph with `node 0 and all nodes(cities)` +3. Connect all nodes with`0`,`[0,i] - i is nodes range from [1,n]`,`0-1` meaning `node 0` and `node 1` connect edge ,value is node `i`'s cost `wells[i]`; +4. Turn cities into nodes, wells' costs and pipes' into costs into edges value which connected into two cities. +5. Sort all edges (from min to max) +6. Scan all edges, check whether 2 nodes connected or not:(`Union-Find`), + - if already connected, continue check next edge + - if not yet connected, +costs, connect 2 nodes +7. If all nodes already connected, get minimum costs, return result +8. (#7 Optimization) for each `union`, total nodes number `n-1`, if `n==0`, then meaning all nodes already connected, can terminate early. + +> Here use weighted-Union-find to check whether 2 nodes connceted or not, and union not connected nodes. + +For example:`n = 5, wells=[1,2,2,3,2], pipes=[[1,2,1],[2,3,1],[4,5,7]]` + +As below pic: + +![minimum cost](../assets/problems/1168.optimize-water-distribution-in-a-village-1.png) + +From pictures, we can see that all nodes already connected with minimum costs. + +#### Complexity Analysis +- *Time Complexity:* `O(ElogE) - E number of edge in graph` +- *Space Complexity:* `O(E)` + + + +> A graph at most have `n(n-1)/2 - n number of nodes in graph` edges ([Complete Graph](https://www.wikiwand.com/en/Complete_graph)) + +## Key Points +1. Build graph with all possible edges. +2. Sort edges by value (costs) +3. Iterate all edges (from min value to max value) +4. For each edges, check whether two nodes already connected (union-find), + - if already connected, then skip + - if not connected, then union two nodes, add costs to result + +## Code (`Java/Python3`) +*Java code* +```java + class OptimizeWaterDistribution { + public int minCostToSupplyWater(int n, int[] wells, int[][] pipes) { + List costs = new ArrayList<>(); + for (int i = 1; i <= n; i++) { + costs.add(new EdgeCost(0, i, wells[i - 1])); + } + for (int[] p : pipes) { + costs.add(new EdgeCost(p[0], p[1], p[2])); + } + Collections.sort(costs); + int minCosts = 0; + UnionFind uf = new UnionFind(n); + for (EdgeCost edge : costs) { + int rootX = uf.find(edge.node1); + int rootY = uf.find(edge.node2); + if (rootX == rootY) continue; + minCosts += edge.cost; + uf.union(edge.node1, edge.node2); + // for each union, we connnect one node + n--; + // if all nodes already connected, terminate early + if (n == 0) { + return minCosts; + } + } + return minCosts; + } + + class EdgeCost implements Comparable { + int node1; + int node2; + int cost; + public EdgeCost(int node1, int node2, int cost) { + this.node1 = node1; + this.node2 = node2; + this.cost = cost; + } + + @Override + public int compareTo(EdgeCost o) { + return this.cost - o.cost; + } + } + + class UnionFind { + int[] parent; + int[] rank; + public UnionFind(int n) { + parent = new int[n + 1]; + for (int i = 0; i <= n; i++) { + parent[i] = i; + } + rank = new int[n + 1]; + } + public int find(int x) { + return x == parent[x] ? x : find(parent[x]); + } + public void union(int x, int y) { + int px = find(x); + int py = find(y); + if (px == py) return; + if (rank[px] >= rank[py]) { + parent[py] = px; + rank[px] += rank[py]; + } else { + parent[px] = py; + rank[py] += rank[px]; + } + } + } + } +``` +*Pythong3 code* +```python +class Solution: + def minCostToSupplyWater(self, n: int, wells: List[int], pipes: List[List[int]]) -> int: + union_find = {i: i for i in range(n + 1)} + + def find(x): + return x if x == union_find[x] else find(union_find[x]) + + def union(x, y): + px = find(x) + py = find(y) + union_find[px] = py + + graph_wells = [[cost, 0, i] for i, cost in enumerate(wells, 1)] + graph_pipes = [[cost, i, j] for i, j, cost in pipes] + min_costs = 0 + for cost, x, y in sorted(graph_wells + graph_pipes): + if find(x) == find(y): + continue + union(x, y) + min_costs += cost + n -= 1 + if n == 0: + return min_costs +``` + +## External Resources + +1. [Shortest path problem](https://www.wikiwand.com/en/Shortest_path_problem) +2. [Dijkstra's algorithm](https://www.wikiwand.com/en/Dijkstra%27s_algorithm) +3. [Floyd–Warshall algorithm](https://www.wikiwand.com/en/Floyd%E2%80%93Warshall_algorithm) +4. [Bellman–Ford algorithm](https://www.wikiwand.com/en/Bellman%E2%80%93Ford_algorithm) +5. [Kruskal's algorithm](https://www.wikiwand.com/en/Kruskal%27s_algorithm) +6. [Prim's algorithm](https://www.wikiwand.com/en/Prim%27s_algorithm) +7. [Minimum spanning tree](https://www.wikiwand.com/en/Minimum_spanning_tree) \ No newline at end of file diff --git a/problems/1186.maximum-subarray-sum-with-one-deletion.md b/problems/1186.maximum-subarray-sum-with-one-deletion.md new file mode 100644 index 0000000..40e15d3 --- /dev/null +++ b/problems/1186.maximum-subarray-sum-with-one-deletion.md @@ -0,0 +1,161 @@ +## 题目地址 + +https://leetcode.com/problems/maximum-subarray-sum-with-one-deletion/ + +## 题目描述 + +``` + +给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。 + +换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中最大的。 + +注意,删除一个元素后,子数组 不能为空。 + +请看示例: + +示例 1: + +输入:arr = [1,-2,0,3] +输出:4 +解释:我们可以选出 [1, -2, 0, 3],然后删掉 -2,这样得到 [1, 0, 3],和最大。 +示例 2: + +输入:arr = [1,-2,-2,3] +输出:3 +解释:我们直接选出 [3],这就是最大和。 +示例 3: + +输入:arr = [-1,-1,-1,-1] +输出:-1 +解释:最后得到的子数组不能为空,所以我们不能选择 [-1] 并从中删去 -1 来得到 0。 + 我们应该直接选择 [-1],或者选择 [-1, -1] 再从中删去一个 -1。 +  + +提示: + +1 <= arr.length <= 10^5 +-10^4 <= arr[i] <= 10^4 + +``` + +## 思路 + +### 暴力法 + +符合知觉的做法是求出所有的情况,然后取出最大的。 我们只需要两层循环接口,外循环用于确定我们丢弃的元素,内循环用于计算subArraySum。 + +```python + class Solution: + def maximumSum(self, arr: List[int]) -> int: + res = arr[0] + def maxSubSum(arr, skip): + res = maxSub = float("-inf") + + for i in range(len(arr)): + if i == skip: + continue + maxSub = max(arr[i], maxSub + arr[i]) + res = max(res, maxSub) + return res + # 这里循环到了len(arr)项,表示的是一个都不删除的情况 + for i in range(len(arr) + 1): + res = max(res, maxSubSum(arr, i)) + return res +``` + +### 空间换时间 + +上面的做法在LC上会TLE, 因此我们需要换一种思路,既然超时了,我们是否可以从空间换时间的角度思考呢?我们可以分别从头尾遍历,建立两个subArraySub的数组l和r。 其实这个不难想到,很多题目都用到了这个技巧。 + +具体做法: + +- 一层遍历, 建立l数组,l[i]表示从左边开始的以arr[i]结尾的subArraySum的最大值 +- 一层遍历, 建立r数组,r[i]表示从右边开始的以arr[i]结尾的subArraySum的最大值 +- 一层遍历, 计算 l[i - 1] + r[i + 1] 的最大值 +> l[i - 1] + r[i + 1]的含义就是删除arr[i]的子数组最大值 +- 上面的这个步骤得到了删除一个的子数组最大值, 不删除的只需要在上面循环顺便计算一下即可 + + + +```python +class Solution: + def maximumSum(self, arr: List[int]) -> int: + n = len(arr) + l = [arr[0]] * n + r = [arr[n - 1]] * n + if n == 1: + return arr[0] + res = arr[0] + for i in range(1, n): + l[i] = max(l[i - 1] + arr[i], arr[i]) + res = max(res, l[i]) + for i in range(n - 2, -1, -1): + r[i] = max(r[i + 1] + arr[i], arr[i]) + res = max(res, r[i]) + for i in range(1, n - 1): + res = max(res, l[i - 1] + r[i + 1]) + + return res + +``` + +### 动态规划 + +上面的算法虽然时间上有所改善,但是正如标题所说,空间复杂度是O(n),有没有办法改进呢?答案是使用动态规划。 + +具体过程: + +- 定义max0,表示以arr[i]结尾且一个都不漏的最大子数组和 +- 定义max1,表示以arr[i]或者arr[i - 1]结尾,可以漏一个的最大子数组和 +- 遍历数组,更新max1和max0(注意先更新max1,因为max1用到了上一个max0) +- 其中` max1 = max(max1 + arr[i], max0)`, 即删除arr[i - 1]或者删除arr[i] +- 其中` max0 = max(max0 + arr[i], arr[i])`, 一个都不删除 + +```python +# +# @lc app=leetcode.cn id=1186 lang=python3 +# +# [1186] 删除一次得到子数组最大和 +# + +# @lc code=start + + +class Solution: + def maximumSum(self, arr: List[int]) -> int: + # DP + max0 = arr[0] + max1 = arr[0] + res = arr[0] + n = len(arr) + if n == 1: + return max0 + + for i in range(1, n): + # 先更新max1,再更新max0,因为max1用到了上一个max0 + max1 = max(max1 + arr[i], max0) + max0 = max(max0 + arr[i], arr[i]) + res = max(res, max0, max1) + return res + + +# @lc code=end + + +``` +## 关键点解析 + +- 空间换时间 +- 头尾双数组 +- 动态规划 + +## 相关题目 + +- [42.trapping-rain-water](./42.trapping-rain-water.md) + + + + + + diff --git a/problems/121.best-time-to-buy-and-sell-stock.md b/problems/121.best-time-to-buy-and-sell-stock.md new file mode 100644 index 0000000..d30c088 --- /dev/null +++ b/problems/121.best-time-to-buy-and-sell-stock.md @@ -0,0 +1,116 @@ + +## 题目地址 +https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/ + +## 题目描述 + +``` +Say you have an array for which the ith element is the price of a given stock on day i. + +If you were only permitted to complete at most one transaction (i.e., buy one and sell one share of the stock), design an algorithm to find the maximum profit. + +Note that you cannot sell a stock before you buy one. + +Example 1: + +Input: [7,1,5,3,6,4] +Output: 5 +Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. + Not 7-1 = 6, as selling price needs to be larger than buying price. +Example 2: + +Input: [7,6,4,3,1] +Output: 0 +Explanation: In this case, no transaction is done, i.e. max profit = 0. +``` + +## 思路 + +由于我们是想获取到最大的利润,我们的策略应该是低点买入,高点卖出。 + +由于题目对于交易次数有限制,只能交易一次,因此问题的本质其实就是求波峰浪谷的差值的最大值。 + +用图表示的话就是这样: + +![](https://tva1.sinaimg.cn/large/0082zybply1gbx7rzp9e1j30jg0c23zs.jpg) + +## 关键点解析 + +- 这类题只要你在心中(或者别的地方)画出上面这种图就很容易解决 + +## 代码 + +语言支持:JS,Python,C++ + +JS Code: + +```js +/** + * @param {number[]} prices + * @return {number} + */ +var maxProfit = function(prices) { + let min = prices[0]; + let profit = 0; + // 7 1 5 3 6 4 + for(let i = 1; i < prices.length; i++) { + if (prices[i] > prices[i -1]) { + profit = Math.max(profit, prices[i] - min); + } else { + min = Math.min(min, prices[i]);; + } + } + + return profit; +}; +``` + + + +Python Code: + +```python +class Solution: + def maxProfit(self, prices: 'List[int]') -> int: + if not prices: return 0 + + min_price = float('inf') + max_profit = 0 + + for price in prices: + if price < min_price: + min_price = price + elif max_profit < price - min_price: + max_profit = price - min_price + return max_profit +``` + +C++ Code: +```c++ +/** + * 系统上C++的测试用例中的输入有[],因此需要加一个判断 + */ +class Solution { +public: + int maxProfit(vector& prices) { + if (prices.empty()) return 0; + auto min = prices[0]; + auto profit = 0; + for (auto i = 1; i < prices.size(); ++i) { + if (prices[i] > prices[i -1]) { + profit = max(profit, prices[i] - min); + } else { + min = std::min(min, prices[i]);; + } + } + return profit; + } +}; +``` + + + +## 相关题目 + +- [122.best-time-to-buy-and-sell-stock-ii](./122.best-time-to-buy-and-sell-stock-ii.md) +- [309.best-time-to-buy-and-sell-stock-with-cooldown](./309.best-time-to-buy-and-sell-stock-with-cooldown.md) diff --git a/problems/1218.longest-arithmetic-subsequence-of-given-difference.md b/problems/1218.longest-arithmetic-subsequence-of-given-difference.md new file mode 100644 index 0000000..06440d4 --- /dev/null +++ b/problems/1218.longest-arithmetic-subsequence-of-given-difference.md @@ -0,0 +1,104 @@ +## 题目地址 + +https://leetcode-cn.com/problems/longest-arithmetic-subsequence-of-given-difference/ + +## 题目描述 + +``` + +给你一个整数数组 arr 和一个整数 difference,请你找出 arr 中所有相邻元素之间的差等于给定 difference 的等差子序列,并返回其中最长的等差子序列的长度。 + +  + +示例 1: + +输入:arr = [1,2,3,4], difference = 1 +输出:4 +解释:最长的等差子序列是 [1,2,3,4]。 +示例 2: + +输入:arr = [1,3,5,7], difference = 1 +输出:1 +解释:最长的等差子序列是任意单个元素。 +示例 3: + +输入:arr = [1,5,7,8,5,3,4,2,1], difference = -2 +输出:4 +解释:最长的等差子序列是 [7,5,3,1]。 +  + +提示: + +1 <= arr.length <= 10^5 +-10^4 <= arr[i], difference <= 10^4 + +``` + +## 思路 + +最直观的思路是双层循环,我们暴力的枚举出以每一个元素为开始元素,以最后元素结尾的的所有情况。很明显这是所有的情况,这就是暴力法的精髓, 很明显这种解法会TLE(超时),不过我们先来看一下代码,顺着这个思维继续思考。 + +### 暴力法 + +```python + def longestSubsequence(self, arr: List[int], difference: int) -> int: + n = len(arr) + res = 1 + for i in range(n): + count = 1 + for j in range(i + 1, n): + if arr[i] + difference * count == arr[j]: + count += 1 + + if count > res: + res = count + + return res +``` +### 动态规划 + +上面的时间复杂度是O(n^2), 有没有办法降低到O(n)呢?很容易想到的是空间换时间的解决方案。 + +我的想法是将`以每一个元素结尾的最长等差子序列的长度`统统存起来,即`dp[num] = maxLen` 这样我们遍历到一个新的元素的时候,就去之前的存储中去找`dp[num - difference]`, 如果找到了,就更新当前的`dp[num] = dp[num - difference] + 1`, 否则就是不进行操作(还是默认值1)。 + +这种空间换时间的做法的时间和空间复杂度都是O(n)。 + + +## 关键点解析 + +- 将`以每一个元素结尾的最长等差子序列的长度`统统存起来 + + +## 代码 + +```python +# +# @lc app=leetcode.cn id=1218 lang=python3 +# +# [1218] 最长定差子序列 +# + +# @lc code=start + + +class Solution: + + # 动态规划 + def longestSubsequence(self, arr: List[int], difference: int) -> int: + n = len(arr) + res = 1 + dp = {} + for num in arr: + dp[num] = 1 + if num - difference in dp: + dp[num] = dp[num - difference] + 1 + + return max(dp.values()) + +# @lc code=end +``` + +## 相关题目 + + + diff --git a/problems/122.best-time-to-buy-and-sell-stock-ii.md b/problems/122.best-time-to-buy-and-sell-stock-ii.md new file mode 100644 index 0000000..142b299 --- /dev/null +++ b/problems/122.best-time-to-buy-and-sell-stock-ii.md @@ -0,0 +1,95 @@ + +## 题目地址 +https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/ + +## 题目描述 + +``` +Say you have an array for which the ith element is the price of a given stock on day i. + +Design an algorithm to find the maximum profit. You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times). + +Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again). + +Example 1: + +Input: [7,1,5,3,6,4] +Output: 7 +Explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4. + Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3. +Example 2: + +Input: [1,2,3,4,5] +Output: 4 +Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4. + Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are + engaging multiple transactions at the same time. You must sell before buying again. +Example 3: + +Input: [7,6,4,3,1] +Output: 0 +Explanation: In this case, no transaction is done, i.e. max profit = 0. +``` + +## 思路 + +由于我们是想获取到最大的利润,我们的策略应该是低点买入,高点卖出。 + +由于题目对于交易次数没有限制,因此只要能够赚钱的机会我们都不应该放过。 + +> 如下图,我们只需要求出加粗部分的总和即可 + +用图表示的话就是这样: + +![122.best-time-to-buy-and-sell-stock-ii](../assets/problems/122.best-time-to-buy-and-sell-stock-ii.png) + +## 关键点解析 + +- 这类题只要你在心中(或者别的地方)画出上面这种图就很容易解决 + +## 代码 + +语言支持:JS,Python + +JS Code: + +```js +/** + * @param {number[]} prices + * @return {number} + */ +var maxProfit = function(prices) { + let profit = 0; + + for(let i = 1; i < prices.length; i++) { + if (prices[i] > prices[i -1]) { + profit = profit + prices[i] - prices[i - 1]; + } + } + + return profit; +}; +``` + + + +Python Code: + +```python +class Solution: + def maxProfit(self, prices: 'List[int]') -> int: + gains = [prices[i] - prices[i-1] for i in range(1, len(prices)) + if prices[i] - prices[i-1] > 0] + return sum(gains) +#评论区里都讲这是一道开玩笑的送分题. +``` + + + + + +## 相关题目 + +- [121.best-time-to-buy-and-sell-stock](./121.best-time-to-buy-and-sell-stock.md) +- [309.best-time-to-buy-and-sell-stock-with-cooldown](./309.best-time-to-buy-and-sell-stock-with-cooldown.md) + diff --git a/problems/1227.airplane-seat-assignment-probability.md b/problems/1227.airplane-seat-assignment-probability.md new file mode 100644 index 0000000..bf9b2ec --- /dev/null +++ b/problems/1227.airplane-seat-assignment-probability.md @@ -0,0 +1,249 @@ +## 题目地址(1227. 飞机座位分配概率) + +https://leetcode-cn.com/problems/airplane-seat-assignment-probability/description/ + +## 题目描述 + +``` + +有 n 位乘客即将登机,飞机正好有 n 个座位。第一位乘客的票丢了,他随便选了一个座位坐下。 + +剩下的乘客将会: + +如果他们自己的座位还空着,就坐到自己的座位上, + +当他们自己的座位被占用时,随机选择其他座位 +第 n 位乘客坐在自己的座位上的概率是多少? + +  + +示例 1: + +输入:n = 1 +输出:1.00000 +解释:第一个人只会坐在自己的位置上。 +示例 2: + +输入: n = 2 +输出: 0.50000 +解释:在第一个人选好座位坐下后,第二个人坐在自己的座位上的概率是 0.5。 +  + +提示: + +1 <= n <= 10^5 + + +``` + +## 暴力递归 + +这是一道 LeetCode 为数不多的概率题,我们来看下。 + +### 思路 + +我们定义原问题为 f(n)。对于第一个人来说,他有 n 中选择,就是分别选择 n 个座位中的一个。由于选择每个位置的概率是相同的,那么选择每个位置的概率应该都是 1 / n。 + +我们分三种情况来讨论: + +- 如果第一个人选择了第一个人的位置(也就是选择了自己的位置),那么剩下的人按照票上的座位做就好了,这种情况第 n 个人一定能做到自己的位置 +- 如果第一个人选择了第 n 个人的位置,那么第 n 个人肯定坐不到自己的位置。 +- 如果第一个人选择了第 i (1 < i < n)个人的位置,那么第 i 个人就相当于变成了“票丢的人”,此时问题转化为 f(n - i + 1)。 + +此时的问题转化关系如图: + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gb12n0omuuj31bc0ju405.jpg) +(红色表示票丢的人) + +整个过程分析: + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gb12nhestaj318u0bg76f.jpg) + +### 代码 + +代码支持 Python3: + +Python3 Code: + +```python +class Solution: + def nthPersonGetsNthSeat(self, n: int) -> float: + if n == 1: + return 1 + if n == 2: + return 0.5 + res = 1 / n + for i in range(2, n): + res += self.nthPersonGetsNthSeat(n - i + 1) * 1 / n + return res +``` + +上述代码会栈溢出。 + +## 暴力递归 + hashtable + +### 思路 + +我们考虑使用记忆化递归来减少重复计算,虽然这种做法可以减少运行时间,但是对减少递归深度没有帮助。还是会栈溢出。 + +### 代码 + +代码支持 Python3: + +Python3 Code: + +```python +class Solution: + seen = {} + + def nthPersonGetsNthSeat(self, n: int) -> float: + if n == 1: + return 1 + if n == 2: + return 0.5 + if n in self.seen: + return self.seen[n] + res = 1 / n + for i in range(2, n): + res += self.nthPersonGetsNthSeat(n - i + 1) * 1 / n + self.seen[n] = res + return res +``` + +## 动态规划 + +### 思路 + +上面做法会栈溢出。其实我们根本不需要运行就应该能判断出栈溢出,题目已经给了数据规模是 1 <= n <= 10 \*\* 5。 这个量级不管什么语言,除非使用尾递归,不然一般都会栈溢出,具体栈深度大家可以查阅相关资料。 + +既然是栈溢出,那么我们考虑使用迭代来完成。 很容易想到使用动态规划来完成。其实递归都写出来,写一个朴素版的动态规划也难不到哪去,毕竟动态规划就是记录子问题,并建立子问题之间映射而已,这和递归并无本质区别。 + +### 代码 + +代码支持 Python3: + +Python3 Code: + +```python +class Solution: + def nthPersonGetsNthSeat(self, n: int) -> float: + if n == 1: + return 1 + if n == 2: + return 0.5 + + dp = [1, .5] * n + + for i in range(2, n): + dp[i] = 1 / n + for j in range(2, i): + dp[i] += dp[i - j + 1] * 1 / n + return dp[-1] +``` + +这种思路的代码超时了,并且仅仅执行了 35/100 testcase 就超时了。 + +## 数学分析 + +### 思路 + +我们还需要进一步优化时间复杂度,我们需要思考是否可以在线形的时间内完成。 + +我们继续前面的思路进行分析, 不难得出,我们不妨称其为等式 1: + +``` +f(n) += 1/n + 0 + 1/n * (f(n-1) + f(n-2) + ... + f(2)) += 1/n * (f(n-1) + f(n-2) + ... + f(2) + 1) += 1/n * (f(n-1) + f(n-2) + ... + f(2) + f(1)) +``` + +似乎更复杂了?没关系,我们继续往下看,我们看下 f(n - 1),我们不妨称其为等式 2。 + +``` +f(n-1) = 1/(n-1) * (f(n-2) + f(n-3) + ... + f(1)) +``` + +我们将等式 1 和等式 2 两边分别同时乘以 n 和 n - 1 + +``` +n * f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(1) +(n-1) * f(n-1) = f(n-2) + f(n-3) + ... + f(1) +``` + +我们将两者相减: + +``` +n * f(n) - (n-1)*f(n-1) = f(n-1) +``` + +我们继续将 (n-1)\*f(n-1) 移到等式右边,得到: + +``` +n * f(n) = n * f(n-1) +``` + +也就是说: + +``` +f(n) = f(n - 1) +``` + +当然前提是 n 大于 2。 + +既然如此,我们就可以减少一层循环, 我们用这个思路来优化一下上面的 dp 解法。这种解法终于可以 AC 了。 + +### 代码 + +代码支持 Python3: + +Python3 Code: + +```python +class Solution: + def nthPersonGetsNthSeat(self, n: int) -> float: + if n == 1: + return 1 + if n == 2: + return 0.5 + + dp = [1, .5] * n + + for i in range(2, n): + dp[i] = 1/n+(n-2)/n * dp[n-1] + return dp[-1] +``` + +## 优化数学分析 + +### 思路 + +上面我们通过数学分析,得出了当 n 大于 2 时: + +``` +f(n) = f(n - 1) +``` + +那么是不是意味着我们随便求出一个 n 就好了? 比如我们求出 n = 2 的时候的值,是不是就知道 n 为任意数的值了。 我们不难想出 n = 2 时候,概率是 0.5,因此只要 n 大于 1 就是 0.5 概率,否则就是 1 概率。 + +### 代码 + +代码支持 Python3: + +Python3 Code: + +```python +class Solution: + def nthPersonGetsNthSeat(self, n: int) -> float: + return 1 if n == 1 else .5 + +``` + +## 关键点 + +- 概率分析 +- 数学推导 +- 动态规划 +- 递归 + mapper +- 栈限制大小 +- 尾递归 diff --git a/problems/124.binary-tree-maximum-path-sum.md b/problems/124.binary-tree-maximum-path-sum.md new file mode 100644 index 0000000..1c4c660 --- /dev/null +++ b/problems/124.binary-tree-maximum-path-sum.md @@ -0,0 +1,154 @@ + +## 题目地址 +https://leetcode.com/problems/binary-tree-maximum-path-sum/description/ + +## 题目描述 + +``` +Given a non-empty binary tree, find the maximum path sum. + +For this problem, a path is defined as any sequence of nodes from some starting node to any node in the tree along the parent-child connections. The path must contain at least one node and does not need to go through the root. + +Example 1: + +Input: [1,2,3] + + 1 + / \ + 2 3 + +Output: 6 +Example 2: + +Input: [-10,9,20,null,null,15,7] + + -10 + / \ + 9 20 + / \ + 15 7 + +Output: 42 +``` + +## 思路 + + 这道题目的path让我误解了,然后浪费了很多时间来解这道题 + 我觉得leetcode给的demo太少了,不足以让我理解path的概念 + 因此我这里自己画了一个图,来补充一下,帮助大家理解path的概念,不要像我一样理解错啦。 + + 首先是官网给的两个例子: + + ![124.binary-tree-maximum-path-sum](../assets/problems/124.binary-tree-maximum-path-sum.jpg) + + 接着是我自己画的一个例子: + + ![124.binary-tree-maximum-path-sum](../assets/problems/124.binary-tree-maximum-path-sum-1.jpg) + +大家可以结合上面的demo来继续理解一下path, 除非你理解了path,否则不要往下看。 + + + 树的题目,基本都是考察递归思想的。因此我们需要思考如何去定义我们的递归函数, + 在这里我定义了一个递归函数,它的功能是,`返回以当前节点为根节点的MathPath` + 但是有两个条件: + + 1. 第一是跟节点必须选择 + 2. 第二是左右子树只能选择一个 + + 为什么要有这两个条件? + + 我的想法是原问题可以转化为: + + 以每一个节点为根节点,我们分别求出max path,最后计算最大值,因此第一个条件需要满足. + + 对于第二个,由于递归函数子节点的返回值会被父节点使用,因此我们如果两个孩子都选择了 + 就不符合max path的定义了,这也是我没有理解题意,绕了很大弯子的原因。 + + + 因此我的做法就是不断调用递归函数,然后在调用过程中不断计算和更新max,最后在主函数中将max返回即可。 + +## 关键点解析 + +- 递归 +- 理解题目中的path定义 + +## 代码 + +代码支持:JavaScript,Java + +- JavaScript + +```js + + +/* + * @lc app=leetcode id=124 lang=javascript + * + * [124] Binary Tree Maximum Path Sum + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function helper(node, payload) { + if (node === null) return 0; + + const l = helper(node.left, payload); + const r = helper(node.right, payload); + + payload.max = Math.max( + node.val + Math.max(0, l) + Math.max(0, r), + payload.max + ); + + return node.val + Math.max(l, r, 0); +} +/** + * @param {TreeNode} root + * @return {number} + */ +var maxPathSum = function(root) { + if (root === null) return 0; + const payload = { + max: root.val + }; + helper(root, payload); + return payload.max; +}; +``` + +- Java + +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +class Solution { + int ans; + public int maxPathSum(TreeNode root) { + ans = Integer.MIN_VALUE; + helper(root); // recursion + return ans; + } + + public int helper(TreeNode root) { + if (root == null) return 0; + int leftMax = Math.max(0, helper(root.left)); // find the max sub-path sum in left sub-tree + int rightMax = Math.max(0, helper(root.right)); // find the max sub-path sum in right sub-tree + ans = Math.max(ans, leftMax+rightMax+root.val); // find the max path sum at current node + return max(leftMax, rightMax) + root.val; // according to the definition of path, the return value of current node can only be that the sum of current node value plus either left or right max path sum. + } +} +``` + +## 相关题目 +- [113.path-sum-ii](./113.path-sum-ii.md) diff --git a/problems/125.valid-palindrome.md b/problems/125.valid-palindrome.md new file mode 100644 index 0000000..b7578be --- /dev/null +++ b/problems/125.valid-palindrome.md @@ -0,0 +1,147 @@ + +## 题目地址 + +https://leetcode.com/problems/valid-palindrome/description/ + +## 题目描述 + +``` +Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases. + +Note: For the purpose of this problem, we define empty string as valid palindrome. + +Example 1: + +Input: "A man, a plan, a canal: Panama" +Output: true +Example 2: + +Input: "race a car" +Output: false + +``` + +## 思路 + +这是一道考察回文的题目,而且是最简单的形式,即判断一个字符串是否是回文。 + +针对这个问题,我们可以使用头尾双指针, + +- 如果两个指针的元素不相同,则直接返回false, +- 如果两个指针的元素相同,我们同时更新头尾指针,循环。 直到头尾指针相遇。 + +时间复杂度为O(n). + +拿“noon”这样一个回文串来说,我们的判断过程是这样的: + +![125.valid-palindrome-1](../assets/problems/125.valid-palindrome-1.png) + +拿“abaa”这样一个不是回文的字符串来说,我们的判断过程是这样的: + +![125.valid-palindrome-2](../assets/problems/125.valid-palindrome-2.png) + + + +## 关键点解析 + +- 双指针 + +## 代码 + +* 语言支持:JS,C++,Python + +JavaScript Code: + +```js + +/* + * @lc app=leetcode id=125 lang=javascript + * + * [125] Valid Palindrome + */ +// 只处理英文字符(题目忽略大小写,我们前面全部转化成了小写, 因此这里我们只判断小写)和数字 +function isValid(c) { + const charCode = c.charCodeAt(0); + const isDigit = + charCode >= "0".charCodeAt(0) && charCode <= "9".charCodeAt(0); + const isChar = charCode >= "a".charCodeAt(0) && charCode <= "z".charCodeAt(0); + + return isDigit || isChar; +} +/** + * @param {string} s + * @return {boolean} + */ +var isPalindrome = function(s) { + s = s.toLowerCase(); + let left = 0; + let right = s.length - 1; + + while (left < right) { + if (!isValid(s[left])) { + left++; + continue; + } + if (!isValid(s[right])) { + right--; + continue; + } + + if (s[left] === s[right]) { + left++; + right--; + } else { + break; + } + } + + return right <= left; +}; +``` +C++ Code: +```C++ +class Solution { +public: + bool isPalindrome(string s) { + if (s.empty()) + return true; + const char* s1 = s.c_str(); + const char* e = s1 + s.length() - 1; + while (e > s1) { + if (!isalnum(*s1)) {++s1; continue;} + if (!isalnum(*e)) {--e; continue;} + if (tolower(*s1) != tolower(*e)) return false; + else {--e; ++s1;} + } + return true; + } +}; +``` + +Python Code: + +```python +class Solution: + def isPalindrome(self, s: str) -> bool: + left, right = 0, len(s) - 1 + while left < right: + if not s[left].isalnum(): + left += 1 + continue + if not s[right].isalnum(): + right -= 1 + continue + if s[left].lower() == s[right].lower(): + left += 1 + right -= 1 + else: + break + return right <= left + + def isPalindrome2(self, s: str) -> bool: + """ + 使用语言特性进行求解 + """ + s = ''.join(i for i in s if i.isalnum()).lower() + return s == s[::-1] +``` diff --git a/problems/1260.shift-2d-grid.md b/problems/1260.shift-2d-grid.md new file mode 100644 index 0000000..1729456 --- /dev/null +++ b/problems/1260.shift-2d-grid.md @@ -0,0 +1,144 @@ +## 题目地址(1260. 二维网格迁移) + +https://leetcode-cn.com/problems/shift-2d-grid/description/ + +## 题目描述 + +``` + +给你一个 n 行 m 列的二维网格 grid 和一个整数 k。你需要将 grid 迁移 k 次。 + +每次「迁移」操作将会引发下述活动: + +位于 grid[i][j] 的元素将会移动到 grid[i][j + 1]。 +位于 grid[i][m - 1] 的元素将会移动到 grid[i + 1][0]。 +位于 grid[n - 1][m - 1] 的元素将会移动到 grid[0][0]。 +请你返回 k 次迁移操作后最终得到的 二维网格。 + +  + +示例 1: + + + +输入:grid = [[1,2,3],[4,5,6],[7,8,9]], k = 1 +输出:[[9,1,2],[3,4,5],[6,7,8]] +示例 2: + + + +输入:grid = [[3,8,1,9],[19,7,2,5],[4,6,11,10],[12,0,21,13]], k = 4 +输出:[[12,0,21,13],[3,8,1,9],[19,7,2,5],[4,6,11,10]] +示例 3: + +输入:grid = [[1,2,3],[4,5,6],[7,8,9]], k = 9 +输出:[[1,2,3],[4,5,6],[7,8,9]] +  + +提示: + +1 <= grid.length <= 50 +1 <= grid[i].length <= 50 +-1000 <= grid[i][j] <= 1000 +0 <= k <= 100 + + +``` + +## 暴力法 + +我们直接翻译题目,没有任何 hack 的做法。 + +### 代码 + +```python +from copy import deepcopy + +class Solution: + def shiftGrid(self, grid: List[List[int]], k: int) -> List[List[int]]: + n = len(grid) + m = len(grid[0]) + for _ in range(k): + old = deepcopy(grid) + for i in range(n): + for j in range(m): + if j == m - 1: + grid[(i + 1) % n][0] = old[i][j] + elif i == n - 1 and j == m - 1: + grid[0][0] = old[i][j] + else: + grid[i][j + 1] = old[i][j] + return grid +``` + +由于是 easy,上述做法勉强可以过,我们考虑优化。 + +## 数学分析 + +### 思路 + +我们仔细观察矩阵会发现,其实这样的矩阵迁移是有规律的。 如图: +![image](https://user-images.githubusercontent.com/12479470/72203575-4f6e4c00-34a8-11ea-8765-03fc856d4ea6.png) + +因此这个问题就转化为我们一直的一维矩阵转移问题,LeetCode 也有原题[189. 旋转数组](https://leetcode-cn.com/problems/rotate-array/),同时我也写了一篇文章[文科生都能看懂的循环移位算法](https://lucifer.ren/blog/2019/12/11/rotate-list/)专门讨论这个,最终我们使用的是三次旋转法,相关数学证明也有写,很详细,这里不再赘述。 + +LeetCode 真的是喜欢换汤不换药呀 😂 + +### 代码 + +Python 代码: + +```python +# +# @lc app=leetcode.cn id=1260 lang=python3 +# +# [1260] 二维网格迁移 +# + +# @lc code=start + + +class Solution: + def shiftGrid(self, grid: List[List[int]], k: int) -> List[List[int]]: + n = len(grid) + m = len(grid[0]) + # 二维到一维 + arr = [grid[i][j] for i in range(n) for j in range(m)] + # 取模,缩小k的范围,避免无意义的运算 + k %= m * n + res = [] + # 首尾交换法 + + def reverse(l, r): + while l < r: + t = arr[l] + arr[l] = arr[r] + arr[r] = t + l += 1 + r -= 1 + # 三次旋转 + reverse(0, m * n - k - 1) + reverse(m * n - k, m * n - 1) + reverse(0, m * n - 1) + # 一维到二维 + row = [] + for i in range(m * n): + if i > 0 and i % m == 0: + res.append(row) + row = [] + row.append(arr[i]) + res.append(row) + + return res + +# @lc code=end + +``` + +## 相关题目 + +- [189. 旋转数组](https://leetcode-cn.com/problems/rotate-array/) + +## 参考 + +- [文科生都能看懂的循环移位算法](https://lucifer.ren/blog/2019/12/11/rotate-list/) diff --git a/problems/1261.find-elements-in-a-contaminated-binary-tree.md b/problems/1261.find-elements-in-a-contaminated-binary-tree.md new file mode 100644 index 0000000..a5d5c5e --- /dev/null +++ b/problems/1261.find-elements-in-a-contaminated-binary-tree.md @@ -0,0 +1,249 @@ +# 题目地址(1261. 在受污染的二叉树中查找元素) + +https://leetcode-cn.com/problems/find-elements-in-a-contaminated-binary-tree/submissions/ + +## 题目描述 + +``` +给出一个满足下述规则的二叉树: + +root.val == 0 +如果 treeNode.val == x 且 treeNode.left != null,那么 treeNode.left.val == 2 * x + 1 +如果 treeNode.val == x 且 treeNode.right != null,那么 treeNode.right.val == 2 * x + 2 +现在这个二叉树受到「污染」,所有的 treeNode.val 都变成了 -1。 + +请你先还原二叉树,然后实现 FindElements 类: + +FindElements(TreeNode* root) 用受污染的二叉树初始化对象,你需要先把它还原。 +bool find(int target) 判断目标值 target 是否存在于还原后的二叉树中并返回结果。 +  + +示例 1: + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gasy4qroxoj308w03b3yi.jpg) + +输入: +["FindElements","find","find"] +[[[-1,null,-1]],[1],[2]] +输出: +[null,false,true] +解释: +FindElements findElements = new FindElements([-1,null,-1]); +findElements.find(1); // return False +findElements.find(2); // return True +示例 2: + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gasy5mlo3mj30b405iwep.jpg) + +输入: +["FindElements","find","find","find"] +[[[-1,-1,-1,-1,-1]],[1],[3],[5]] +输出: +[null,true,true,false] +解释: +FindElements findElements = new FindElements([-1,-1,-1,-1,-1]); +findElements.find(1); // return True +findElements.find(3); // return True +findElements.find(5); // return False +示例 3: + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gasy5sr25yj308i07maa8.jpg) + +输入: +["FindElements","find","find","find","find"] +[[[-1,null,-1,-1,null,-1]],[2],[3],[4],[5]] +输出: +[null,true,false,false,true] +解释: +FindElements findElements = new FindElements([-1,null,-1,-1,null,-1]); +findElements.find(2); // return True +findElements.find(3); // return False +findElements.find(4); // return False +findElements.find(5); // return True +  + +提示: + +TreeNode.val == -1 +二叉树的高度不超过 20 +节点的总数在 [1, 10^4] 之间 +调用 find() 的总次数在 [1, 10^4] 之间 +0 <= target <= 10^6 + +``` + +## 暴力法 + +### 思路 + +最简单想法就是递归建立树,然后 find 的时候递归查找即可,代码也很简单。 + +### 代码 + +Pythpn Code: + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class FindElements: + node = None + def __init__(self, root: TreeNode): + def recover(node): + if not node: + return node; + if node.left: + node.left.val = 2 * node.val + 1 + if node.right: + node.right.val = 2 * node.val + 2 + recover(node.left) + recover(node.right) + return node + root.val = 0 + self.node = recover(root) + + + def find(self, target: int) -> bool: + def findInTree(node, target): + if not node: + return False + if node.val == target: + return True + return findInTree(node.left, target) or findInTree(node.right, target) + return findInTree(self.node, target) + + + + +# Your FindElements object will be instantiated and called as such: +# obj = FindElements(root) +# param_1 = obj.find(target) +``` + +上述代码会超时,我们来考虑优化。 + +## 空间换时间 + +### 思路 + +上述代码会超时,我们考虑使用空间换时间。 建立树的时候,我们将所有值存到一个集合中去。当需要 find 的时候,我们直接查找 set 即可,时间复杂度 O(1)。 + +### 代码 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class FindElements: + def __init__(self, root: TreeNode): + # set 不能放在init外侧。 因为测试用例之间不会销毁FindElements的变量 + self.seen = set() + def recover(node): + if not node: + return node; + if node.left: + node.left.val = 2 * node.val + 1 + self.seen.add(node.left.val) + if node.right: + node.right.val = 2 * node.val + 2 + self.seen.add(node.right.val) + recover(node.left) + recover(node.right) + return node + root.val = 0 + self.seen.add(0) + self.node = recover(root) + + + def find(self, target: int) -> bool: + return target in self.seen + + + + +# Your FindElements object will be instantiated and called as such: +# obj = FindElements(root) +# param_1 = obj.find(target) +``` + +这种解法可以 AC,但是在数据量非常大的时候,可能 MLE,我们继续考虑优化。 + +## 二进制法 + +### 思路 + +这是一种非常巧妙的做法。 + +如果我们把树中的数全部加 1 会怎么样? + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gasypfuvuvj30rs0kudjr.jpg) +(图参考 https://leetcode.com/problems/find-elements-in-a-contaminated-binary-tree/discuss/431229/Python-Special-Way-for-find()-without-HashSet-O(1)-Space-O(logn)-Time) + +仔细观察发现,每一行的左右子树分别有不同的前缀: + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gasz0x09koj312y0sgnnt.jpg) + +Ok,那么算法就来了。为了便于理解,我们来举个具体的例子,比如 target 是 9,我们首先将其加 1,二进制表示就是 1010。不考虑第一位,就是 010,我们只要: + +- 0 向左 👈 +- 1 向右 👉 +- - 0 向左 👈 + +就可以找到 9 了。 + +> 0 表示向左 , 1 表示向右 + +### 代码 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class FindElements: + node = None + def __init__(self, root: TreeNode): + def recover(node): + if not node: + return node; + if node.left: + node.left.val = 2 * node.val + 1 + if node.right: + node.right.val = 2 * node.val + 2 + recover(node.left) + recover(node.right) + return node + root.val = 0 + self.node = recover(root) + + + def find(self, target: int) -> bool: + node = self.node + for bit in bin(target+1)[3:]: + node = node and (node.left, node.right)[int(bit)] + return bool(node) + + + + +# Your FindElements object will be instantiated and called as such: +# obj = FindElements(root) +# param_1 = obj.find(target) +``` + +## 关键点解析 + +- 空间换时间 +- 二进制思维 +- 将 target + 1 diff --git a/problems/1262.greatest-sum-divisible-by-three.md b/problems/1262.greatest-sum-divisible-by-three.md new file mode 100644 index 0000000..a6d3802 --- /dev/null +++ b/problems/1262.greatest-sum-divisible-by-three.md @@ -0,0 +1,241 @@ +# 题目地址(1262. 可被三整除的最大和) + +https://leetcode-cn.com/problems/greatest-sum-divisible-by-three/description/ + +## 题目描述 + +``` +给你一个整数数组 nums,请你找出并返回能被三整除的元素最大和。 + +  + +示例 1: + +输入:nums = [3,6,5,1,8] +输出:18 +解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。 +示例 2: + +输入:nums = [4] +输出:0 +解释:4 不能被 3 整除,所以无法选出数字,返回 0。 +示例 3: + +输入:nums = [1,2,3,4,4] +输出:12 +解释:选出数字 1, 3, 4 以及 4,它们的和是 12(可被 3 整除的最大和)。 +  + +提示: + +1 <= nums.length <= 4 * 10^4 +1 <= nums[i] <= 10^4 + +``` + +## 暴力法 + +### 思路 + +一种方式是找出所有的能够被 3 整除的子集,然后挑选出和最大的。由于我们选出了所有的子集,那么时间复杂度就是 $O(2^N)$ , 毫无疑问会超时。这里我们使用回溯法找子集,如果不清楚回溯法,可以参考我之前的题解,很多题目都用到了,比如[78.subsets](https://github.com/azl397985856/leetcode/blob/master/problems/78.subsets.md)。 + +更多回溯题目,可以访问上方链接查看(可以使用一套模板搞定): + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gatuu0yfh8j30f60c4ab3.jpg) + +### 代码 + +```python +class Solution: + def maxSumDivThree(self, nums: List[int]) -> int: + self.res = 0 + def backtrack(temp, start): + total = sum(temp) + if total % 3 == 0: + self.res = max(self.res, total) + for i in range(start, len(nums)): + temp.append(nums[i]) + backtrack(temp, i + 1) + temp.pop(-1) + + + backtrack([], 0) + + return self.res +``` + +## 减法 + 排序 + +减法的核心思想是,我们求出总和。如果总和不满足题意,我们尝试减去最小的数,使之满足题意。 + +### 思路 + +这种算法的思想,具体来说就是: + +- 我们将所有的数字加起来,我们不妨设为 total +- total 除以 3,得到一个余数 mod, mod 可能值有 0,1,2. +- 同时我们建立两个数组,一个是余数为 1 的数组 one,一个是余数为 2 的数组 two +- 如果 mod 为 0,我们直接返回即可。 +- 如果 mod 为 1,我们可以减去 one 数组中最小的一个(如果有的话),或者减去两个 two 数组中最小的(如果有的话),究竟减去谁取决谁更小。 +- 如果 mod 为 2,我们可以减去 two 数组中最小的一个(如果有的话),或者减去两个 one 数组中最小的(如果有的话),究竟减去谁取决谁更小。 + +由于我们需要取 one 和 two 中最小的一个或者两个,因此对数组 one 和 two 进行排序是可行的,如果基于排序的话,时间复杂度大致为 $O(NlogN)$,这种算法可以通过。 + +以题目中的例 1 为例: + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gatvdy1zb1j30u00x2wgx.jpg) + +以题目中的例 2 为例: + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gatvem1xm4j30u00xvq59.jpg) + +### 代码 + +```python +class Solution: + def maxSumDivThree(self, nums: List[int]) -> int: + one = [] + two = [] + total = 0 + + for num in nums: + total += num + if num % 3 == 1: + one.append(num) + if num % 3 == 2: + two.append(num) + one.sort() + two.sort() + if total % 3 == 0: + return total + elif total % 3 == 1 and one: + if len(two) >= 2 and one[0] > two[0] + two[1]: + return total - two[0] - two[1] + return total - one[0] + elif total % 3 == 2 and two: + if len(one) >= 2 and two[0] > one[0] + one[1]: + return total - one[0] - one[1] + return total - two[0] + return 0 +``` + +## 减法 + 非排序 + +### 思路 + +上面的解法使用到了排序。 我们其实观察发现,我们只是用到了 one 和 two 的最小的两个数。因此我们完全可以在线形的时间和常数的空间完成这个算法。我们只需要分别记录 one 和 two 的最小值和次小值即可,在这里,我使用了两个长度为 2 的数组来表示,第一项是最小值,第二项是次小值。 + +### 代码 + +```python +class Solution: + def maxSumDivThree(self, nums: List[int]) -> int: + one = [float('inf')] * 2 + two = [float('inf')] * 2 + total = 0 + + for num in nums: + total += num + if num % 3 == 1: + if num < one[0]: + t = one[0] + one[0] = num + one[1] = t + elif num < one[1]: + one[1] = num + if num % 3 == 2: + if num < two[0]: + t = two[0] + two[0] = num + two[1] = t + elif num < two[1]: + two[1] = num + if total % 3 == 0: + return total + elif total % 3 == 1 and one: + if len(two) >= 2 and one[0] > two[0] + two[1]: + return total - two[0] - two[1] + return total - one[0] + elif total % 3 == 2 and two: + if len(one) >= 2 and two[0] > one[0] + one[1]: + return total - one[0] - one[1] + return total - two[0] + return 0 +``` + +## 有限状态机 + +### 思路 + +我在[数据结构与算法在前端领域的应用 - 第二篇](https://lucifer.ren/blog/2019/09/19/algorthimn-fe-2/) 中讲到了有限状态机。 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gatub3vftxj30eq0bfta0.jpg) + +状态机表示若干个状态以及在这些状态之间的转移和动作等行为的数学模型。通俗的描述状态机就是定义了一套状态変更的流程:状态机包含一个状态集合,定义当状态机处于某一个状态的时候它所能接收的事件以及可执行的行为,执行完成后,状态机所处的状态。 + +状态机使用非常广泛,比如正则表达式的引擎,编译器的词法和语法分析,网络协议,企业应用等很多领域都会用到。 + +拿本题中来说,我们从左到右扫描数组的过程,将会不断改变状态机的状态。 + +我们使用 state 数组来表示本题的状态: + +- state[0] 表示 mod 为 0 的 最大和 +- state[1] 表示 mod 为 1 的 最大和 +- state[2] 表示 mod 为 1 的 最大和 + +我们的状态转移方程就会很容易。说到状态转移方程,你可能会想到动态规划。没错!这种思路可以直接翻译成动态规划,算法完全一样。如果你看过我上面提到的文章,那么状态转移方程对你来说就会很容易。如果你不清楚,那么请往下看: + +- 我们从左往右不断读取数字,我们不妨设这个数字为 num。 +- 如果 num % 3 为 0。 那么我们的 state[0], state[1], state[2] 可以直接加上 num(题目限定了 num 为非负), 因为任何数字加上 3 的倍数之后,mod 3 的值是不变的。 +- 如果 num % 3 为 1。 我们知道 state[2] + num 会变成一个能被三整除的数,但是这个数字不一定比当前的 state[0]大。 代码表示就是`max(state[2] + num, state[0])`。同理 state[1] 和 state[2] 的转移逻辑类似。 +- 同理 num % 3 为 2 也是类似的逻辑。 +- 最后我们返回 state[0]即可。 + +### 代码 + +```python +class Solution: + def maxSumDivThree(self, nums: List[int]) -> int: + state = [0, float('-inf'), float('-inf')] + + for num in nums: + if num % 3 == 0: + state = [state[0] + num, state[1] + num, state[2] + num] + if num % 3 == 1: + a = max(state[2] + num, state[0]) + b = max(state[0] + num, state[1]) + c = max(state[1] + num, state[2]) + state = [a, b, c] + if num % 3 == 2: + a = max(state[1] + num, state[0]) + b = max(state[2] + num, state[1]) + c = max(state[0] + num, state[2]) + state = [a, b, c] + return state[0] +``` + +当然这个代码还可以简化: + +```python +class Solution: + def maxSumDivThree(self, nums: List[int]) -> int: + state = [0, float('-inf'), float('-inf')] + + for num in nums: + temp = [0] * 3 + for i in range(3): + temp[(i + num) % 3] = max(state[(i + num) % 3], state[i] + num) + state = temp + + return state[0] +``` + +## 关键点解析 + +- 贪婪法 +- 状态机 +- 数学分析 + +## 扩展 + +实际上,我们可以采取加法(贪婪策略),感兴趣的可以试一下。 diff --git a/problems/128.longest-consecutive-sequence.md b/problems/128.longest-consecutive-sequence.md new file mode 100644 index 0000000..f7d4bce --- /dev/null +++ b/problems/128.longest-consecutive-sequence.md @@ -0,0 +1,116 @@ +## 题目地址 + +https://leetcode.com/problems/longest-consecutive-sequence/description/ + +## 题目描述 + +``` +Given an unsorted array of integers, find the length of the longest consecutive elements sequence. + +Your algorithm should run in O(n) complexity. + +Example: + +Input: [100, 4, 200, 1, 3, 2] +Output: 4 +Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4. +Accepted +200,786 +Submissions +485,346 + +``` + +## 思路 + +这是一道最最长连续数字序列长度的题目, 官网给出的难度是`hard`. + +符合直觉的做法是先排序,然后用一个变量记录最大值,遍历去更新最大值即可, + +代码: + +```js +if (nums.length === 0) return 0; +let count = 1; +let maxCount = 1; +// 这里其实可以不需要排序,这么做只不过是为了方便理解 +nums = [...new Set(nums)].sort((a, b) => a - b); +for (let i = 0; i < nums.length - 1; i++) { + if (nums[i + 1] - nums[i] === 1) { + count++; + } else { + if (count > maxCount) { + maxCount = count; + } + count = 1; + } +} +return Math.max(count, maxCount); +``` + +但是需要排序时间复杂度会上升,题目要求时间复杂度为 O(n), +那么我们其实可以不用排序去解决的。 + +思路就是将之前”排序之后,通过比较前后元素是否相差 1 来判断是否连续“的思路改为 +不排序而是`直接遍历,然后在内部循环里面查找是否存在当前值的邻居元素`,但是马上有一个 +问题,内部我们`查找是否存在当前值的邻居元素`的过程如果使用数组时间复杂度是 O(n), +那么总体的复杂度就是 O(n^2),完全不可以接受。怎么办呢? + +我们换个思路,用空间来换时间。比如用类似于 hashmap 这样的数据结构优化查询部分,将时间复杂度降低到 O(1), 代码见后面`代码部分` + +## 关键点解析 + +- 空间换时间 + +## 代码 + +```js +/* + * @lc app=leetcode id=128 lang=javascript + * + * [128] Longest Consecutive Sequence + * + * https://leetcode.com/problems/longest-consecutive-sequence/description/ + * + * algorithms + * Hard (40.98%) + * Total Accepted: 200.3K + * Total Submissions: 484.5K + * Testcase Example: '[100,4,200,1,3,2]' + * + * Given an unsorted array of integers, find the length of the longest + * consecutive elements sequence. + * + * Your algorithm should run in O(n) complexity. + * + * Example: + * + * + * Input: [100, 4, 200, 1, 3, 2] + * Output: 4 + * Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. + * Therefore its length is 4. + * + * + */ +/** + * @param {number[]} nums + * @return {number} + */ +var longestConsecutive = function(nums) { + nums = new Set(nums); + let max = 0; + let y = 0; + nums.forEach(x => { + // 说明x是连续序列的开头元素 + if (!nums.has(x - 1)) { + y = x + 1; + while (nums.has(y)) { + y = y + 1; + } + max = Math.max(max, y - x); // y - x 就是从x开始到最后有多少连续的数字 + } + }); + return max; +}; +``` diff --git a/problems/129.sum-root-to-leaf-numbers.md b/problems/129.sum-root-to-leaf-numbers.md new file mode 100644 index 0000000..551629c --- /dev/null +++ b/problems/129.sum-root-to-leaf-numbers.md @@ -0,0 +1,244 @@ +## 题目地址 + +https://leetcode.com/problems/sum-root-to-leaf-numbers/description/ + +## 题目描述 + +``` +Given a binary tree containing digits from 0-9 only, each root-to-leaf path could represent a number. + +An example is the root-to-leaf path 1->2->3 which represents the number 123. + +Find the total sum of all root-to-leaf numbers. + +Note: A leaf is a node with no children. + +Example: + +Input: [1,2,3] + 1 + / \ + 2 3 +Output: 25 +Explanation: +The root-to-leaf path 1->2 represents the number 12. +The root-to-leaf path 1->3 represents the number 13. +Therefore, sum = 12 + 13 = 25. +Example 2: + +Input: [4,9,0,5,1] + 4 + / \ + 9 0 + / \ +5 1 +Output: 1026 +Explanation: +The root-to-leaf path 4->9->5 represents the number 495. +The root-to-leaf path 4->9->1 represents the number 491. +The root-to-leaf path 4->0 represents the number 40. +Therefore, sum = 495 + 491 + 40 = 1026. + +``` + +## 思路 + +这是一道非常适合训练递归的题目。虽然题目不难,但是要想一次写正确,并且代码要足够优雅却不是很容易。 + +这里我们的思路是定一个递归的helper函数,用来帮助我们完成递归操作。 +递归函数的功能是将它的左右子树相加,注意这里不包括这个节点本身,否则会多加, +我们其实关注的就是叶子节点的值,然后通过层层回溯到root,返回即可。 + +整个过程如图所示: + +![129.sum-root-to-leaf-numbers-1](../assets/problems/129.sum-root-to-leaf-numbers-1.jpg) + + +那么数字具体的计算逻辑,如图所示,相信大家通过这个不难发现规律: + +![129.sum-root-to-leaf-numbers-2](../assets/problems/129.sum-root-to-leaf-numbers-2.jpg) + +## 关键点解析 + +- 递归分析 + +## 代码 + +* 语言支持:JS,C++,Python + +JavaScipt Code: + +```js +/* + * @lc app=leetcode id=129 lang=javascript + * + * [129] Sum Root to Leaf Numbers + */ +function helper(node, cur) { + if (node === null) return 0; + const next = node.val + cur * 10; + + if (node.left === null && node.right === null) return next; + + const l = helper(node.left, next); + const r = helper(node.right, next); + + return l + r; +} +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number} + */ +var sumNumbers = function(root) { + // tag: `tree` `dfs` `math` + return helper(root, 0); +}; +``` + +C++ Code: +```C++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +public: + int sumNumbers(TreeNode* root) { + return helper(root, 0); + } +private: + int helper(const TreeNode* root, int val) { + if (root == nullptr) return 0; + auto ret = root->val + val * 10; + if (root->left == nullptr && root->right == nullptr) + return ret; + auto l = helper(root->left, ret); + auto r = helper(root->right, ret); + return l + r; + } +}; +``` + +Python Code: + +```python +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def sumNumbers(self, root: TreeNode) -> int: + + def helper(node, cur_val): + if not node: return 0 + next_val = cur_val * 10 + node.val + + if not (node.left or node.right): + return next_val + + left_val = helper(node.left, next_val) + right_val = helper(node.right, next_val) + + return left_val + right_val + + return helper(root, 0) +``` + +## 拓展 + +通常来说,可以利用队列、栈等数据结构将递归算法转为递推算法。 + +### 描述 + +使用两个队列: +1. 当前和队列:保存上一层每个结点的当前和(比如49和40) +2. 结点队列:保存当前层所有的非空结点 + +每次循环按层处理结点队列。处理步骤: +1. 从结点队列取出一个结点 +2. 从当前和队列将上一层对应的当前和取出来 +3. 若左子树非空,则将该值乘以10加上左子树的值,并添加到当前和队列中 +4. 若右子树非空,则将该值乘以10加上右子树的值,并添加到当前和队列中 +5. 若左右子树均为空时,将该节点的当前和加到返回值中 + +## 实现 + +* 语言支持:C++,Python + +C++ Code: + +```C++ +class Solution { +public: + int sumNumbers(TreeNode* root) { + if (root == nullptr) return 0; + auto ret = 0; + auto runningSum = vector{root->val}; + auto queue = vector{root}; + while (!queue.empty()) { + auto sz = queue.size(); + for (auto i = 0; i < sz; ++i) { + auto n = queue.front(); + queue.erase(queue.begin()); + auto tmp = runningSum.front(); + runningSum.erase(runningSum.begin()); + if (n->left != nullptr) { + runningSum.push_back(tmp * 10 + n->left->val); + queue.push_back(n->left); + } + if (n->right != nullptr) { + runningSum.push_back(tmp * 10 + n->right->val); + queue.push_back(n->right); + } + if (n->left == nullptr && n->right == nullptr) { + ret += tmp; + } + } + } + return ret; + } +}; +``` + +Python Code: + +```python +class Solution: + def sumNumbers(self, root: TreeNode) -> int: + if not root: return 0 + result = 0 + node_queue, sum_queue = [root], [root.val] + while node_queue: + for i in node_queue: + cur_node = node_queue.pop(0) + cur_val = sum_queue.pop(0) + if cur_node.left: + node_queue.append(cur_node.left) + sum_queue.append(cur_val * 10 + cur_node.left.val) + if cur_node.right: + node_queue.append(cur_node.right) + sum_queue.append(cur_val * 10 + cur_node.right.val) + if not (cur_node.left or cur_node.right): + result += cur_val + return result +``` + +## 相关题目 + +- [sum-of-root-to-leaf-binary-numbers](https://leetcode.com/problems/sum-of-root-to-leaf-binary-numbers/) + +> 这道题和本题太像了,跟一道题没啥区别 diff --git a/problems/1297.maximum-number-of-occurrences-of-a-substring.md b/problems/1297.maximum-number-of-occurrences-of-a-substring.md new file mode 100644 index 0000000..194fea2 --- /dev/null +++ b/problems/1297.maximum-number-of-occurrences-of-a-substring.md @@ -0,0 +1,144 @@ +# 题目地址(1297. 子串的最大出现次数) + +https://leetcode-cn.com/problems/maximum-number-of-occurrences-of-a-substring + +## 题目描述 + +``` +给你一个字符串 s ,请你返回满足以下条件且出现次数最大的 任意 子串的出现次数: + +子串中不同字母的数目必须小于等于 maxLetters 。 +子串的长度必须大于等于 minSize 且小于等于 maxSize 。 + + +示例 1: + +输入:s = "aababcaab", maxLetters = 2, minSize = 3, maxSize = 4 +输出:2 +解释:子串 "aab" 在原字符串中出现了 2 次。 +它满足所有的要求:2 个不同的字母,长度为 3 (在 minSize 和 maxSize 范围内)。 +示例 2: + +输入:s = "aaaa", maxLetters = 1, minSize = 3, maxSize = 3 +输出:2 +解释:子串 "aaa" 在原字符串中出现了 2 次,且它们有重叠部分。 +示例 3: + +输入:s = "aabcabcab", maxLetters = 2, minSize = 2, maxSize = 3 +输出:3 +示例 4: + +输入:s = "abcde", maxLetters = 2, minSize = 3, maxSize = 3 +输出:0 + + +提示: + +1 <= s.length <= 10^5 +1 <= maxLetters <= 26 +1 <= minSize <= maxSize <= min(26, s.length) +s 只包含小写英文字母。 +``` + +## 暴力法 + +题目给的数据量不是很大,为 1 <= maxLetters <= 26,我们试一下暴力法。 + +### 思路 + +暴力法如下: + +- 先找出所有满足长度大于等于 minSize 且小于等于 maxSize 的所有子串。(平方的复杂度) +- 对于 maxLetter 满足题意的子串,我们统计其出现次数。时间复杂度为 O(k),其中 k 为子串长度 +- 返回最大的出现次数 + +### 代码 + +Pythpn Code: + +```python +class Solution: + def maxFreq(self, s: str, maxLetters: int, minSize: int, maxSize: int) -> int: + n = len(s) + letters = set() + cnts = dict() + res = 0 + for i in range(n - minSize + 1): + length = minSize + while i + length <= n and length <= maxSize: + t = s[i:i + length] + for c in t: + if len(letters) > maxLetters: + break + letters.add(c) + if len(letters) <= maxLetters: + cnts[t] = cnts.get(t, 0) + 1 + res = max(res, cnts[t]) + letters.clear() + length += 1 + return res +``` + +上述代码会超时。我们来利用剪枝来优化。 + +## 剪枝 + +### 思路 + +还是暴力法的思路,不过我们在此基础上进行一些优化。首先我们需要仔细阅读题目,如果你足够细心或者足够有经验,可能会发现其实题目中 maxSize 没有任何用处,属于干扰信息。 + +也就是说我们没有必要统计`长度大于等于 minSize 且小于等于 maxSize 的所有子串`,而是统计长度为 minSize 的所有字串即可。原因是,如果一个大于 minSize 长度的字串若是满足条件,那么该子串其中必定有至少一个长度为 minSize 的字串满足条件。因此一个大于 minSize 长度的字串出现了 n 次,那么该子串其中必定有一个长度为 minSize 的子串出现了 n 次。 + +### 代码 + +代码支持 Python3,Java: + +Python Code: + +```python + def maxFreq(self, s: str, maxLetters: int, minSize: int, maxSize: int) -> int: + counter, res = {}, 0 + for i in range(0, len(s) - minSize + 1): + sub = s[i : i + minSize] + if len(set(sub)) <= maxLetters: + counter[sub] = counter.get(sub, 0) + 1 + res = max(res, counter[sub]) + return res; + +# @lc code=end +``` + +Java Code: + +```java + public int maxFreq(String s, int maxLetters, int minSize, int maxSize) { + Map counter = new HashMap<>(); + int res = 0; + for (int i = 0; i < s.length() - minSize + 1; i++) { + String substr = s.substring(i, i + minSize); + if (checkNum(substr, maxLetters)) { + int newVal = counter.getOrDefault(substr, 0) + 1; + counter.put(substr, newVal); + res = Math.max(res, newVal); + } + } + return res; +} +public boolean checkNum(String substr, int maxLetters) { + Set set = new HashSet<>(); + for (int i = 0; i < substr.length(); i++) + set.add(substr.charAt(i)); + return set.size() <= maxLetters; +} + +``` + +## 关键点解析 + +- 滑动窗口 +- 识别题目干扰信息 +- 看题目限制条件,对于本题有用的信息是`1 <= maxLetters <= 26` + +## 扩展 + +我们也可以使用滑动窗口来解决,感兴趣的可以试试看。 diff --git a/problems/130.surrounded-regions.md b/problems/130.surrounded-regions.md new file mode 100644 index 0000000..060ec58 --- /dev/null +++ b/problems/130.surrounded-regions.md @@ -0,0 +1,159 @@ +## 题目地址 + +https://leetcode.com/problems/surrounded-regions/description/ + +## 题目描述 + +``` +Given a 2D board containing 'X' and 'O' (the letter O), capture all regions surrounded by 'X'. + +A region is captured by flipping all 'O's into 'X's in that surrounded region. + +Example: + +X X X X +X O O X +X X O X +X O X X +After running your function, the board should be: + +X X X X +X X X X +X X X X +X O X X +Explanation: + +Surrounded regions shouldn’t be on the border, which means that any 'O' on the border of the board are not flipped to 'X'. Any 'O' that is not on the border and it is not connected to an 'O' on the border will be flipped to 'X'. Two cells are connected if they are adjacent cells connected horizontally or vertically. + +``` + +## 思路 + +我们需要将所有被X包围的O变成X,并且题目明确说了边缘的所有O都是不可以变成X的。 + +![130.surrounded-regions](../assets/problems/130.surrounded-regions-1.jpg) + +其实我们观察会发现,我们除了边缘的O以及和边缘O连通的O是不需要变成X的,其他都要变成X。 + +经过上面的思考,问题转化为连通区域问题。 这里我们需要标记一下`边缘的O以及和边缘O连通的O`。 +我们当然可以用额外的空间去存,但是对于这道题目而言,我们完全可以mutate。这样就空间复杂度会好一点。 + +整个过程如图所示: + +> 我将`边缘的O以及和边缘O连通的O` 标记为了 "A" + +![130.surrounded-regions](../assets/problems/130.surrounded-regions-2.jpg) + + + +## 关键点解析 + +- 二维数组DFS解题模板 +- 转化问题为`连通区域问题` +- 直接mutate原数组,节省空间 + +## 代码 + +* 语言支持:JS,Python3 + +```js + + + +/* + * @lc app=leetcode id=130 lang=javascript + * + * [130] Surrounded Regions + */ +// 将O以及周边的O转化为A +function mark(board, i, j, rows, cols) { + if (i < 0 || i > rows - 1 || j < 0 || j > cols - 1 || board[i][j] !== "O") + return; + + board[i][j] = "A"; + mark(board, i + 1, j, rows, cols); + mark(board, i - 1, j, rows, cols); + mark(board, i, j + 1, rows, cols); + mark(board, i, j - 1, rows, cols); +} +/** + * @param {character[][]} board + * @return {void} Do not return anything, modify board in-place instead. + */ +var solve = function(board) { + const rows = board.length; + if (rows === 0) return []; + const cols = board[0].length; + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (i === 0 || i == rows - 1 || j === 0 || j === cols - 1) { + mark(board, i, j, rows, cols); + } + } + } + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (board[i][j] === "O") { + board[i][j] = "X"; + } else if (board[i][j] === "A") { + board[i][j] = "O"; + } + } + } + + return board; +}; +``` +Python Code: +```python +class Solution: + def solve(self, board: List[List[str]]) -> None: + """ + Do not return anything, modify board in-place instead. + """ + # 如果数组长或宽小于等于2,则不需要替换 + if len(board) <= 2 or len(board[0]) <= 2: + return + + row, col = len(board), len(board[0]) + + def dfs(i, j): + """ + 深度优先算法,如果符合条件,替换为A并进一步测试,否则停止 + """ + if i < 0 or j < 0 or i >= row or j >= col or board[i][j] != 'O': + return + board[i][j] = 'A' + + dfs(i - 1, j) + dfs(i + 1, j) + dfs(i, j - 1) + dfs(i, j + 1) + + # 从外围开始 + for i in range(row): + dfs(i, 0) + dfs(i, col-1) + + for j in range(col): + dfs(0, j) + dfs(row-1, j) + + # 最后完成替换 + for i in range(row): + for j in range(col): + if board[i][j] == 'O': + board[i][j] = 'X' + elif board[i][j] == 'A': + board[i][j] = 'O' +``` + +## 相关题目 + +- [200.number-of-islands](./200.number-of-islands.md) + +> 解题模板是一样的 + + diff --git a/problems/131.palindrome-partitioning.md b/problems/131.palindrome-partitioning.md new file mode 100644 index 0000000..e241a9d --- /dev/null +++ b/problems/131.palindrome-partitioning.md @@ -0,0 +1,120 @@ + +## 题目地址 + +https://leetcode.com/problems/palindrome-partitioning/description/ + +## 题目描述 + +``` +Given a string s, partition s such that every substring of the partition is a palindrome. + +Return all possible palindrome partitioning of s. + +Example: + +Input: "aab" +Output: +[ + ["aa","b"], + ["a","a","b"] +] + +``` + +## 思路 + +这是一道求解所有可能性的题目, 这时候可以考虑使用回溯法。 回溯法解题的模板我们已经在很多题目中用过了, +这里就不多说了。大家可以结合其他几道题目加深一下理解。 + + +## 关键点解析 + +- 回溯法 + +## 代码 + +* 语言支持:JS,Python3 + +```js + + +/* + * @lc app=leetcode id=131 lang=javascript + * + * [131] Palindrome Partitioning + */ + +function isPalindrom(s) { + let left = 0; + let right = s.length - 1; + + while(left < right && s[left] === s[right]) { + left++; + right--; + } + + return left >= right; +} + function backtrack(s, list, tempList, start) { + const sliced = s.slice(start); + + if (isPalindrom(sliced) && (tempList.join("").length === s.length)) list.push([...tempList]); + + for(let i = 0; i < sliced.length; i++) { + const sub = sliced.slice(0, i + 1); + if (isPalindrom(sub)) { + tempList.push(sub); + } else { + continue; + } + backtrack(s, list, tempList, start + i + 1); + tempList.pop(); + } + } +/** + * @param {string} s + * @return {string[][]} + */ +var partition = function(s) { + // "aab" + // ["aa", "b"] + // ["a", "a", "b"] + const list = []; + backtrack(s, list, [], 0); + return list; +}; + + +``` +```python +class Solution: + def partition(self, s: str) -> List[List[str]]: + """回溯法""" + + res = [] + + def helper(s, tmp): + """ + 如果是空字符串,说明已经处理完毕 + 否则逐个字符往前测试,判断是否是回文 + 如果是,则处理剩余字符串,并将已经得到的列表作为参数 + """ + if not s: + res.append(tmp) + for i in range(1, len(s) + 1): + if s[:i] == s[:i][::-1]: + helper(s[i:], tmp + [s[:i]]) + + helper(s, []) + return res +``` + +## 相关题目 +- [39.combination-sum](./39.combination-sum.md) +- [40.combination-sum-ii](./40.combination-sum-ii.md) +- [46.permutations](./46.permutations.md) +- [47.permutations-ii](./47.permutations-ii.md) +- [78.subsets](./78.subsets.md) +- [90.subsets-ii](./90.subsets-ii.md) +- [113.path-sum-ii](./113.path-sum-ii.md) + diff --git a/problems/1310.xor-queries-of-a-subarray.md b/problems/1310.xor-queries-of-a-subarray.md new file mode 100644 index 0000000..cf9292f --- /dev/null +++ b/problems/1310.xor-queries-of-a-subarray.md @@ -0,0 +1,168 @@ +# 题目地址(1310. 子数组异或查询) + +https://leetcode-cn.com/problems/xor-queries-of-a-subarray + +## 题目描述 + +``` +有一个正整数数组 arr,现给你一个对应的查询数组 queries,其中 queries[i] = [Li, Ri]。 + +对于每个查询 i,请你计算从 Li 到 Ri 的 XOR 值(即 arr[Li] xor arr[Li+1] xor ... xor arr[Ri])作为本次查询的结果。 + +并返回一个包含给定查询 queries 所有结果的数组。 + + + +示例 1: + +输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]] +输出:[2,7,14,8] +解释: +数组中元素的二进制表示形式是: +1 = 0001 +3 = 0011 +4 = 0100 +8 = 1000 +查询的 XOR 值为: +[0,1] = 1 xor 3 = 2 +[1,2] = 3 xor 4 = 7 +[0,3] = 1 xor 3 xor 4 xor 8 = 14 +[3,3] = 8 +示例 2: + +输入:arr = [4,8,2,10], queries = [[2,3],[1,3],[0,0],[0,3]] +输出:[8,0,4,4] + + +提示: + +1 <= arr.length <= 3 * 10^4 +1 <= arr[i] <= 10^9 +1 <= queries.length <= 3 * 10^4 +queries[i].length == 2 +0 <= queries[i][0] <= queries[i][1] < arr.length +``` + +## 暴力法 + +### 思路 + +最直观的思路是双层循环即可,果不其然超时了。 + +### 代码 + +```python + +class Solution: + def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]: + res = [] + for (L, R) in queries: + i = L + xor = 0 + while i <= R: + xor ^= arr[i] + i += 1 + res.append(xor) + return res +``` + +## 前缀表达式 + +### 思路 + +比较常见的是前缀和,这个概念其实很容易理解,即一个数组中,第 n 位存储的是数组前 n 个数字的和。 + +对 [1,2,3,4,5,6] 来说,其前缀和可以是 pre=[1,3,6,10,15,21]。我们可以使用公式 pre[𝑖]=pre[𝑖−1]+nums[𝑖]得到每一位前缀和的值,从而通过前缀和进行相应的计算和解题。其实前缀和的概念很简单,但困难的是如何在题目中使用前缀和以及如何使用前缀和的关系来进行解题。 + +这道题是前缀对前缀异或,我们利用了异或的性质 `x ^ y ^ x = y`。 + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gaqll5r048j30fm0bfglz.jpg) + +### 代码 + +代码支持 Python3,Java,C++: + +Python Code: + +```python +# +# @lc app=leetcode.cn id=1218 lang=python3 +# +# [1218] 最长定差子序列 +# + +# @lc code=start + + +class Solution: + def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]: + pre = [0] + res = [] + for i in range(len(arr)): + pre.append(pre[i] ^ arr[i]) + for (L, R) in queries: + res.append(pre[L] ^ pre[R + 1]) + return res + +# @lc code=end +``` + +Java Code: + +```java + public int[] xorQueries(int[] arr, int[][] queries) { + + int[] preXor = new int[arr.length]; + preXor[0] = 0; + + for (int i = 1; i < arr.length; i++) + preXor[i] = preXor[i - 1] ^ arr[i - 1]; + + int[] res = new int[queries.length]; + + for (int i = 0; i < queries.length; i++) { + + int left = queries[i][0], right = queries[i][1]; + res[i] = arr[right] ^ preXor[right] ^ preXor[left]; + } + + return res; + } + +``` + +C++ Code: + +```c++ +class Solution { +public: + vector xorQueries(vector& arr, vector>& queries) { + vectorres; + for(int i=1; itemp :queries){ + if(temp[0]==0){ + res.push_back(arr[temp[1]]); + } + else{ + res.push_back(arr[temp[0]-1]^arr[temp[1]]); + } + } + return res; + } +}; +``` + +## 关键点解析 + +- 异或的性质 x ^ y ^ x = y +- 前缀表达式 + +## 相关题目 + +- [303. 区域和检索 - 数组不可变](https://leetcode-cn.com/problems/range-sum-query-immutable/description/) + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gaql7eqyg6j30u00ft0vx.jpg) + +- [1186.删除一次得到子数组最大和](https://lucifer.ren/blog/2019/12/11/leetcode-1186/) diff --git a/problems/1332.remove-palindromic-subsequences.md b/problems/1332.remove-palindromic-subsequences.md new file mode 100644 index 0000000..fe4b081 --- /dev/null +++ b/problems/1332.remove-palindromic-subsequences.md @@ -0,0 +1,97 @@ +# 题目地址(1332. 删除回文子序列) + +https://leetcode-cn.com/problems/remove-palindromic-subsequences/ + +## 题目描述 + +``` +给你一个字符串 s,它仅由字母 'a' 和 'b' 组成。每一次删除操作都可以从 s 中删除一个回文 子序列。 + +返回删除给定字符串中所有字符(字符串为空)的最小删除次数。 + +「子序列」定义:如果一个字符串可以通过删除原字符串某些字符而不改变原字符顺序得到,那么这个字符串就是原字符串的一个子序列。 + +「回文」定义:如果一个字符串向后和向前读是一致的,那么这个字符串就是一个回文。 + +  + +示例 1: + +输入:s = "ababa" +输出:1 +解释:字符串本身就是回文序列,只需要删除一次。 +示例 2: + +输入:s = "abb" +输出:2 +解释:"abb" -> "bb" -> "". +先删除回文子序列 "a",然后再删除 "bb"。 +示例 3: + +输入:s = "baabb" +输出:2 +解释:"baabb" -> "b" -> "". +先删除回文子序列 "baab",然后再删除 "b"。 +示例 4: + +输入:s = "" +输出:0 +  + +提示: + +0 <= s.length <= 1000 +s 仅包含字母 'a'  和 'b' +在真实的面试中遇到过这道题? +``` + +## 思路 + +这又是一道“抖机灵”的题目,类似的题目有[1297.maximum-number-of-occurrences-of-a-substring](https://github.com/azl397985856/leetcode/blob/77db8fa47c7ee0a14b320f7c2d22f7c61ae53c35/problems/1297.maximum-number-of-occurrences-of-a-substring.md) + +由于只有 a 和 b 两个字符。其实最多的消除次数就是 2。因为我们无论如何都可以先消除全部的 1 再消除全部的 2(先消除 2 也一样),这样只需要两次即可完成。 我们再看一下题目给的一次消除的情况,题目给的例子是“ababa”,我们发现其实它本身就是一个回文串,所以才可以一次全部消除。那么思路就有了: + +- 如果 s 是回文,则我们需要一次消除 +- 否则需要两次 +- 一定要注意特殊情况, 对于空字符串,我们需要 0 次 + +## 代码 + +代码支持:Python3 + +Python3 Code: + +```python + +class Solution: + def removePalindromeSub(self, s: str) -> int: + if s == '': + return 0 + def isPalindrome(s): + l = 0 + r = len(s) - 1 + while l < r: + if s[l] != s[r]: + return False + l += 1 + r -= 1 + return True + return 1 if isPalindrome(s) else 2 +``` + +如果你觉得判断回文不是本题重点,也可以简单实现: + +Python3 Code: + +```python +class Solution: + def removePalindromeSub(self, s: str) -> int: + if s == '': + return 0 + return 1 if s == s[::-1] else 2 + +``` + +## 关键点解析 + +- 注意审题目,一定要利用题目条件“只含有 a 和 b 两个字符”否则容易做的很麻烦 diff --git a/problems/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance.md b/problems/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance.md new file mode 100644 index 0000000..cb0de61 --- /dev/null +++ b/problems/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance.md @@ -0,0 +1,118 @@ +# 题目地址(1334. 阈值距离内邻居最少的城市) + +https://leetcode-cn.com/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/ + +## 题目描述 + +``` +有 n 个城市,按从 0 到 n-1 编号。给你一个边数组 edges,其中 edges[i] = [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边,距离阈值是一个整数 distanceThreshold。 + +返回能通过某些路径到达其他城市数目最少、且路径距离 最大 为 distanceThreshold 的城市。如果有多个这样的城市,则返回编号最大的城市。 + +注意,连接城市 i 和 j 的路径的距离等于沿该路径的所有边的权重之和。 + +  + +示例 1: + +``` + +![image.png](http://ww1.sinaimg.cn/large/e9f490c8ly1gbh9v5ygtsj20qo0k0aap.jpg) + +``` + + + +输入:n = 4, edges = [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], distanceThreshold = 4 +输出:3 +解释:城市分布图如上。 +每个城市阈值距离 distanceThreshold = 4 内的邻居城市分别是: +城市 0 -> [城市 1, 城市 2]  +城市 1 -> [城市 0, 城市 2, 城市 3]  +城市 2 -> [城市 0, 城市 1, 城市 3]  +城市 3 -> [城市 1, 城市 2]  +城市 0 和 3 在阈值距离 4 以内都有 2 个邻居城市,但是我们必须返回城市 3,因为它的编号最大。 +示例 2: + +``` + +![image.png](http://ww1.sinaimg.cn/large/e9f490c8ly1gbh9vg1w43j20qo0k0js8.jpg) + +``` + +输入:n = 5, edges = [[0,1,2],[0,4,8],[1,2,3],[1,4,2],[2,3,1],[3,4,1]], distanceThreshold = 2 +输出:0 +解释:城市分布图如上。  +每个城市阈值距离 distanceThreshold = 2 内的邻居城市分别是: +城市 0 -> [城市 1]  +城市 1 -> [城市 0, 城市 4]  +城市 2 -> [城市 3, 城市 4]  +城市 3 -> [城市 2, 城市 4] +城市 4 -> [城市 1, 城市 2, 城市 3]  +城市 0 在阈值距离 4 以内只有 1 个邻居城市。 +  + +提示: + +2 <= n <= 100 +1 <= edges.length <= n * (n - 1) / 2 +edges[i].length == 3 +0 <= fromi < toi < n +1 <= weighti, distanceThreshold <= 10^4 +所有 (fromi, toi) 都是不同的。 + + +``` + +## 思路 + +这道题的本质就是: + +1. 在一个无向图中寻找每两个城镇的最小距离,我们使用 Floyd-Warshall 算法(英语:Floyd-Warshall algorithm),中文亦称弗洛伊德算法,是解决任意两点间的最短路径的一种算法。 +2. 筛选最小距离不大于  distanceThreshold 的城镇。 +3. 统计每个城镇,其满足条件的城镇有多少个 +4. 我们找出最少的即可 + +Floyd-Warshall 算法的时间复杂度和空间复杂度都是$O(N^3)$, 而空间复杂度可以优化到$O(N^2)$。Floyd-Warshall 的基本思想是对于每两个点之间的最小距离,要么经过中间节点 k,要么不经过,我们取两者的最小值,这是一种动态规划思想,详细的解法可以参考[Floyd-Warshall 算法(wikipedia)](https://zh.wikipedia.org/wiki/Floyd-Warshall%E7%AE%97%E6%B3%95) + +## 代码 + +代码支持:Python3 + +Python3 Code: + +```python +class Solution: + def findTheCity(self, n: int, edges: List[List[int]], distanceThreshold: int) -> int: + # 构建dist矩阵 + dist = [[float('inf')] * n for _ in range(n)] + for i, j, w in edges: + dist[i][j] = w + dist[j][i] = w + for i in range(n): + dist[i][i] = 0 + for k in range(n): + for i in range(n): + for j in range(n): + dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]) + + # 过滤 + res = 0 + minCnt = float('inf') + for i in range(n): + cnt = 0 + for d in dist[i]: + if d <= distanceThreshold: + cnt += 1 + if cnt <= minCnt: + minCnt = cnt + res = i + return res + + +``` + +## 关键点解析 + +- Floyd-Warshall 算法 +- 你可以将本文给的 Floyd-Warshall 算法当成一种解题模板使用 diff --git a/problems/136.single-number.md b/problems/136.single-number.md new file mode 100644 index 0000000..b309968 --- /dev/null +++ b/problems/136.single-number.md @@ -0,0 +1,147 @@ +## 题目地址 + +https://leetcode.com/problems/single-number/description/ + +## 题目描述 + +``` +Given a non-empty array of integers, every element appears twice except for one. Find that single one. + +Note: + +Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? +``` + +## 思路 + +根据题目描述,由于加上了时间复杂度必须是 O(n),并且空间复杂度为 O(1)的条件,因此不能用排序方法,也不能使用 map 数据结构。 + +我们可以利用二进制异或的性质来完成,将所有数字异或即得到唯一出现的数字。 + +## 关键点 + +1. 异或的性质 + 两个数字异或的结果`a^b`是将 a 和 b 的二进制每一位进行运算,得出的数字。 运算的逻辑是 + 如果同一位的数字相同则为 0,不同则为 1 + +2. 异或的规律 + +- 任何数和本身异或则为`0` + +- 任何数和 0 异或是`本身` + +3. 很多人只是记得异或的性质和规律,但是缺乏对其本质的理解,导致很难想到这种解法(我本人也没想到) + +4. bit 运算 + +## 代码 + +* 语言支持:JS,C++,Python + +JavaScrip Code: +```js +/* + * @lc app=leetcode id=136 lang=javascript + * + * [136] Single Number + * + * https://leetcode.com/problems/single-number/description/ + * + * algorithms + * Easy (59.13%) + * Total Accepted: 429.3K + * Total Submissions: 724.1K + * Testcase Example: '[2,2,1]' + * + * Given a non-empty array of integers, every element appears twice except for + * one. Find that single one. + * + * Note: + * + * Your algorithm should have a linear runtime complexity. Could you implement + * it without using extra memory? + * + * Example 1: + * + * + * Input: [2,2,1] + * Output: 1 + * + * + * Example 2: + * + * + * Input: [4,1,2,1,2] + * Output: 4 + * + * + */ +/** + * @param {number[]} nums + * @return {number} + */ +var singleNumber = function(nums) { + let ret = 0; + for (let index = 0; index < nums.length; index++) { + const element = nums[index]; + ret = ret ^ element; + } + return ret; +}; +``` +C++: +```C++ +class Solution { +public: + int singleNumber(vector& nums) { + auto ret = 0; + for (auto i : nums) ret ^= i; + return ret; + } +}; + +// C++ one-liner +class Solution { +public: + int singleNumber(vector& nums) { + return accumulate(nums.cbegin(), nums.cend(), 0, bit_xor()); + } +}; +``` + +Python Code: + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + single_number = 0 + for num in nums: + single_number ^= num + return single_number +``` + +## 延伸 + +有一个 n 个元素的数组,除了两个数只出现一次外,其余元素都出现两次,让你找出这两个只出现一次的数分别是几,要求时间复杂度为 O(n) 且再开辟的内存空间固定(与 n 无关)。 + +和上面一样,只是这次不是一个数字,而是两个数字。还是按照上面的思路,我们进行一次全员异或操作, +得到的结果就是那两个只出现一次的不同的数字的异或结果。 + +我们刚才讲了异或的规律中有一个`任何数和本身异或则为0`, 因此我们的思路是能不能将这两个不同的数字分成两组 A 和 B。 +分组需要满足两个条件. + +1. 两个独特的的数字分成不同组 + +2. 相同的数字分成相同组 + +这样每一组的数据进行异或即可得到那两个数字。 + +问题的关键点是我们怎么进行分组呢? + +由于异或的性质是,同一位相同则为 0,不同则为 1. 我们将所有数字异或的结果一定不是 0,也就是说至少有一位是 1. + +我们随便取一个, 分组的依据就来了, 就是你取的那一位是 0 分成 1 组,那一位是 1 的分成一组。 +这样肯定能保证`2. 相同的数字分成相同组`, 不同的数字会被分成不同组么。 很明显当然可以, 因此我们选择是 1,也就是 +说`两个独特的的数字`在那一位一定是不同的,因此两个独特元素一定会被分成不同组。 + +Done! diff --git a/problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.en.md b/problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.en.md new file mode 100644 index 0000000..479e3ca --- /dev/null +++ b/problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.en.md @@ -0,0 +1,359 @@ +## Problem + +https://leetcode.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/ + +## Description + +``` +Given the string s, return the size of the longest substring containing each vowel an even number of times. That is, 'a', 'e', 'i', 'o', and 'u' must appear an even number of times. + +Example 1: + +Input: s = "eleetminicoworoep" +Output: 13 +Explanation: The longest substring is "leetminicowor" which contains two each of the vowels: e, i and o and zero of the vowels: a and u. + +Example 2: + +Input: s = "leetcodeisgreat" +Output: 5 +Explanation: The longest substring is "leetc" which contains two e's. + +Example 3: + +Input: s = "bcbcbc" +Output: 6 +Explanation: In this case, the given string "bcbcbc" is the longest because all vowels: a, e, i, o and u appear zero times. + +Constraints: + +1 <= s.length <= 5 x 10^5 +s contains only lowercase English letters. +``` + +## Approach1: Brute Force and Pruning + +### Algorithm + +My first thought on this problem is to try it with the sliding window technique, which is abandoned immediately because we need to expand or narrow the range of the sliding window (a resizable one), which is not an easy job in this particular case for that this problem involves parity, not like the ones asking for "the longest substring with the most vowels". + +Suddenly I'm at a loss, so I decide to try brute force, which is simple and straightforward. + +- Find all the substrings by using two loops. +- Use a variable `max` to record the maximum length of the substring that meets the condition. +- For each substring, count the numbers of vowels. If all numbers are even, update `max`. + +I do a little trick in the implementation. While enumerating all possible substrings, I start with the longest one, in which case an early return can be achieved, that is, the desired result will be returned once it's found, eliminating the necessity to maintain maximum value. We get less code and higher efficiency in this way. + +### Code(`Python3/JavaScript`) + +Python3 Code: +```py +class Solution: + def findTheLongestSubstring(self, s: str) -> int: + for i in range(len(s), 0, -1): + for j in range(len(s) - i + 1): + sub = s[j:j + i] + has_odd_vowel = False + for vowel in ['a', 'e', 'i', 'o', 'u']: + if sub.count(vowel) % 2 != 0: + has_odd_vowel = True + break + if not has_odd_vowel: return i + return 0 +``` + +JavaScript Code: +```js + * @param {string} s + * @return {number} + */ +var findTheLongestSubstring = function (s) { + const vowels = ['a', 'e', 'i', 'o', 'u'] + const hasEvenVowels = s => !vowels.some(v => (s.match(new RegExp(v, 'g'))||[]).length % 2 !== 0) + + for (let subStrLen = s.length; subStrLen >= 0; subStrLen--) { + let remove = s.length - subStrLen + 1 + + for (let start = 0; start < remove; start++) { + let subStr = s.slice(start, start + subStrLen) + if (hasEvenVowels(subStr)) { + return subStrLen + } + } + } +}; +``` + +### Complexity Analysis + +- Time complexity: $O(n^3)$. Considering every substring takes $O(n^2)$ time. For each of the subarray we calculate the numbers of vowels taking $O(n^2)$ time in the worst case, taking a total of $O(n^3)$ time. +- Space complexity: $O(1)$. + +## Approach2: Prefix Sum + Pruning + +### Algorithm + +Notice that in the last approach there is a step for `counting the numbers of vowels for each substring`. If we look closely, we will discover a lot of duplicate computations. Optimization at this part will get us better efficiency. + +For problems involving consecutive numbers, we can consider using prefix sum to get to a better solution. + +By using this strategy we trade space complexity for time complexity, reducing the time complexity to $O(n ^ 2)$, while increasing the space complexity to $O(n)$, which is a worthwhile trade-off in many situations. + +### Code(`Python3/Java/JavaScript`) + +Python3 Code: +```py +class Solution: + i_mapper = { + "a": 0, + "e": 1, + "i": 2, + "o": 3, + "u": 4 + } + def check(self, s, pre, l, r): + for i in range(5): + if s[l] in self.i_mapper and i == self.i_mapper[s[l]]: cnt = 1 + else: cnt = 0 + if (pre[r][i] - pre[l][i] + cnt) % 2 != 0: return False + return True + def findTheLongestSubstring(self, s: str) -> int: + n = len(s) + + pre = [[0] * 5 for _ in range(n)] + + # pre + for i in range(n): + for j in range(5): + if s[i] in self.i_mapper and self.i_mapper[s[i]] == j: + pre[i][j] = pre[i - 1][j] + 1 + else: + pre[i][j] = pre[i - 1][j] + for i in range(n - 1, -1, -1): + for j in range(n - i): + if self.check(s, pre, j, i + j): + return i + 1 + return 0 +``` + +Java Code: +```java +class Solution { + public int findTheLongestSubstring(String s) { + + int len = s.length(); + + if (len == 0) + return 0; + + int[][] preSum = new int[len][5]; + int start = getIndex(s.charAt(0)); + if (start != -1) + preSum[0][start]++; + + // preSum + for (int i = 1; i < len; i++) { + + int idx = getIndex(s.charAt(i)); + + for (int j = 0; j < 5; j++) { + + if (idx == j) + preSum[i][j] = preSum[i - 1][j] + 1; + else + preSum[i][j] = preSum[i - 1][j]; + } + } + + for (int i = len - 1; i >= 0; i--) { + + for (int j = 0; j < len - i; j++) { + if (checkValid(preSum, s, i, i + j)) + return i + 1 + } + } + return 0 + } + + + public boolean checkValid(int[][] preSum, String s, int left, int right) { + + int idx = getIndex(s.charAt(left)); + + for (int i = 0; i < 5; i++) + if (((preSum[right][i] - preSum[left][i] + (idx == i ? 1 : 0)) & 1) == 1) + return false; + + return true; + } + public int getIndex(char ch) { + + if (ch == 'a') + return 0; + else if (ch == 'e') + return 1; + else if (ch == 'i') + return 2; + else if (ch == 'o') + return 3; + else if (ch == 'u') + return 4; + else + return -1; + } +} +``` + +JavaScript Code: +```js +/** + * @param {string} s + * @return {number} + */ +var findTheLongestSubstring = function (s) { + const prefixes = Array(s.length + 1).fill(0).map(el => Array(5).fill(0)) + const vowels = { + a: 0, + e: 1, + i: 2, + o: 3, + u: 4 + } + + for (let i = 1; i < s.length + 1; i++) { + const letter = s[i - 1] + for (let j = 0; j < 5; j++) { + prefixes[i][j] = prefixes[i - 1][j] + } + if (letter in vowels) { + prefixes[i][vowels[letter]] = prefixes[i - 1][vowels[letter]] + 1 + } + } + + const check = (s, prefixes, l, r) => { + for (let i = 0; i < 5; i++) { + const count = s[l] in vowels && vowels[s[l]] === i + if ((prefixes[r + 1][i] - prefixes[l + 1][i] + count) % 2 !== 0) { + return false + } + } + return true + } + + for (let r = s.length - 1; r >= 0; r--) { + for (let l = 0; l < s.length - r; l++) { + if (check(s, prefixes, l, l + r)) { + return r + 1 + } + } + } + + return 0 +}; +``` + +### Complexity Analysis + +- Time complexity: $O(n^2)$. +- Space complexity: $O(n)$. + +## Approach 3: Prefix Sum + State Compression + +### Algorithm + +In approach 2 we reduce the time complexity by trading space (prefix) for time. However, the time complexity of $O(n^2)$ is still a lot. Is there still room for optimization? + +All we care about is parity. We don't need to count the specific number of occurrences of each vowel. Instead, we can use two states `odd or even`. Since we only need to deal with two states, we can consider using bit operation. + +- Use a 5-bit binary to represent the parity of the number of occurrences of each vowel with 0 for even and 1 for odd. +- The 5 bits of the binary represent 'uoiea' respectively. For example, `10110` means the current substring includes even numbers of 'a' and 'o' and odd numbers of 'e', 'i', and 'u'. +- This binary is assigned to `cur` in the code below. + +Why are we using 0 for even numbers and 1 for odd numbers? Keep reading. + +This algorithm involves elementary mathematics knowledge. + +- If two numbers are of the same parity, then the subtraction must be even. +- If two numbers are of different parity, then the subtraction must be odd. + +Now let's look at the question again. `Why are we using 0 for even numbers and 1 for odd numbers?` Because we want to use the XOR bitwise operation, which works as follows. + +- If XOR is performed on the two binaries, each bit will be bit-operated. +-If two bits are the same, we get 0, otherwise, we get 1. + +This is very similar to the above mathematics knowledge. If the parity of two numbers is the same, we get an even number, otherwise, we get an odd number. So it is natural to use 0 for even numbers and 1 for odd numbers. + +### Code(`Python3/JavaScript`) + +Python3 Code: +```py +class Solution: + def findTheLongestSubstring(self, s: str) -> int: + mapper = { + "a": 1, + "e": 2, + "i": 4, + "o": 8, + "u": 16 + } + seen = {0: -1} + res = cur = 0 + + for i in range(len(s)): + if s[i] in mapper: + cur ^= mapper.get(s[i]) + # If all numbers are of the same parity, then the subtraction must be even. + if cur in seen: + res = max(res, i - seen.get(cur)) + else: + seen[cur] = i + return res +``` + +JavaScript Code: +```js +/** + * @param {string} s + * @return {number} + */ +var findTheLongestSubstring = function (s) { + const mapper = { + "a": 1, + "e": 2, + "i": 4, + "o": 8, + "u": 16 + } + + let max = 0, cur = 0 + const seen = { 0: -1 } + for (let i = 0; i < s.length; i++) { + if (s[i] in mapper) { + cur ^= mapper[s[i]] + } + if (cur in seen) { + max = Math.max(max, i - seen[cur]) + } + else { + seen[cur] = i + } + } + + return max +}; +``` + +### Complexity Analysis + +- Time complexity: $O(n)$. +- Space complexity: $O(n)$. + +## Keypoints + +- Prefix Sum +- State Compression + +## Extension + +- [You can do whatever you want while mastering prefix sum](https://lucifer.ren/blog/2020/01/09/1310.xor-queries-of-a-subarray/)(Chinese) diff --git a/problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md b/problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md new file mode 100644 index 0000000..72620d5 --- /dev/null +++ b/problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md @@ -0,0 +1,265 @@ +# 题目地址(1371. 每个元音包含偶数次的最长子字符串) + +https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/ + +## 题目描述 + +``` +给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。 + +  + +示例 1: + +输入:s = "eleetminicoworoep" +输出:13 +解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。 +示例 2: + +输入:s = "leetcodeisgreat" +输出:5 +解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。 +示例 3: + +输入:s = "bcbcbc" +输出:6 +解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。 +  + +提示: + +1 <= s.length <= 5 x 10^5 +s 只包含小写英文字母。 + +``` + +## 暴力法 + 剪枝 + +### 思路 + +首先拿到这道题的时候,我想到第一反应是滑动窗口行不行。 但是很快这个想法就被我否定了,因为滑动窗口(这里是可变滑动窗口)我们需要扩张和收缩窗口大小,而这里不那么容易。因为题目要求的是奇偶性,而不是类似“元音出现最多的子串”等。 + +突然一下子没了思路。那就试试暴力法吧。暴力法的思路比较朴素和直观。 那就是`双层循环找到所有子串,然后对于每一个子串,统计元音个数,如果子串的元音个数都是偶数,则更新答案,最后返回最大的满足条件的子串长度即可`。 + +这里我用了一个小的 trick。枚举所有子串的时候,我是从最长的子串开始枚举的,这样我找到一个满足条件的直接返回就行了(early return),不必维护最大值。`这样不仅减少了代码量,还提高了效率。` + +### 代码 + +代码支持:Python3 + +Python3 Code: + +```python + +class Solution: + def findTheLongestSubstring(self, s: str) -> int: + for i in range(len(s), 0, -1): + for j in range(len(s) - i + 1): + sub = s[j:j + i] + has_odd_vowel = False + for vowel in ['a', 'e', 'i', 'o', 'u']: + if sub.count(vowel) % 2 != 0: + has_odd_vowel = True + break + if not has_odd_vowel: return i + return 0 + +``` + +**复杂度分析** + +- 时间复杂度:双层循环找出所有子串的复杂度是$O(n^2)$,统计元音个数复杂度也是$O(n)$,因此这种算法的时间复杂度为$O(n^3)$。 +- 空间复杂度:$O(1)$ + +## 前缀和 + 剪枝 + +### 思路 + +上面思路中`对于每一个子串,统计元音个数`,我们仔细观察的话,会发现有很多重复的统计。那么优化这部分的内容就可以获得更好的效率。 + +对于这种连续的数字问题,这里我们考虑使用[前缀和](https://oi-wiki.org/basic/prefix-sum/)来优化。 + +经过这种空间换时间的策略之后,我们的时间复杂度会降低到$O(n ^ 2)$,但是相应空间复杂度会上升到$O(n)$,这种取舍在很多情况下是值得的。 + +### 代码 + +代码支持:Python3,Java + +Python3 Code: + +```python +class Solution: + i_mapper = { + "a": 0, + "e": 1, + "i": 2, + "o": 3, + "u": 4 + } + def check(self, s, pre, l, r): + for i in range(5): + if s[l] in self.i_mapper and i == self.i_mapper[s[l]]: cnt = 1 + else: cnt = 0 + if (pre[r][i] - pre[l][i] + cnt) % 2 != 0: return False + return True + def findTheLongestSubstring(self, s: str) -> int: + n = len(s) + + pre = [[0] * 5 for _ in range(n)] + + # pre + for i in range(n): + for j in range(5): + if s[i] in self.i_mapper and self.i_mapper[s[i]] == j: + pre[i][j] = pre[i - 1][j] + 1 + else: + pre[i][j] = pre[i - 1][j] + for i in range(n - 1, -1, -1): + for j in range(n - i): + if self.check(s, pre, j, i + j): + return i + 1 + return 0 +``` + +Java Code: + +```java +class Solution { + public int findTheLongestSubstring(String s) { + + int len = s.length(); + + if (len == 0) + return 0; + + int[][] preSum = new int[len][5]; + int start = getIndex(s.charAt(0)); + if (start != -1) + preSum[0][start]++; + + // preSum + for (int i = 1; i < len; i++) { + + int idx = getIndex(s.charAt(i)); + + for (int j = 0; j < 5; j++) { + + if (idx == j) + preSum[i][j] = preSum[i - 1][j] + 1; + else + preSum[i][j] = preSum[i - 1][j]; + } + } + + for (int i = len - 1; i >= 0; i--) { + + for (int j = 0; j < len - i; j++) { + if (checkValid(preSum, s, i, i + j)) + return i + 1 + } + } + return 0 + } + + + public boolean checkValid(int[][] preSum, String s, int left, int right) { + + int idx = getIndex(s.charAt(left)); + + for (int i = 0; i < 5; i++) + if (((preSum[right][i] - preSum[left][i] + (idx == i ? 1 : 0)) & 1) == 1) + return false; + + return true; + } + public int getIndex(char ch) { + + if (ch == 'a') + return 0; + else if (ch == 'e') + return 1; + else if (ch == 'i') + return 2; + else if (ch == 'o') + return 3; + else if (ch == 'u') + return 4; + else + return -1; + } +} +``` + +**复杂度分析** + +- 时间复杂度:$O(n^2)$。 +- 空间复杂度:$O(n)$ + +## 前缀和 + 状态压缩 + +### 思路 + +前面的前缀和思路,我们通过空间(prefix)换取时间的方式降低了时间复杂度。但是时间复杂度仍然是平方,我们是否可以继续优化呢? + +实际上由于我们只关心奇偶性,并不关心每一个元音字母具体出现的次数。因此我们可以使用`是奇数,是偶数`两个状态来表示,由于只有两个状态,我们考虑使用位运算。 + +我们使用 5 位的二进制来表示以 i 结尾的字符串中包含各个元音的奇偶性,其中 0 表示偶数,1 表示奇数,并且最低位表示 a,然后依次是 e,i,o,u。比如 `10110` 则表示的是包含偶数个 a 和 o,奇数个 e,i,u,我们用变量 `cur` 来表示。 + +为什么用 0 表示偶数?1 表示奇数? + +回答这个问题,你需要继续往下看。 + +其实这个解法还用到了一个性质,这个性质是小学数学知识: + +- 如果两个数字奇偶性相同,那么其相减一定是偶数。 +- 如果两个数字奇偶性不同,那么其相减一定是奇数。 + +看到这里,我们再来看上面抛出的问题`为什么用 0 表示偶数?1 表示奇数?`。因为这里我们打算用异或运算,而异或的性质是: + +如果对两个二进制做异或,会对其每一位进行位运算,如果相同则位 0,否则位 1。这和上面的性质非常相似。上面说`奇偶性相同则位偶数,否则为奇数`。因此很自然地`用 0 表示偶数?1 表示奇数`会更加方便。 + +### 代码 + +代码支持:Python3 + +Python3 Code: + +```python + +class Solution: + def findTheLongestSubstring(self, s: str) -> int: + mapper = { + "a": 1, + "e": 2, + "i": 4, + "o": 8, + "u": 16 + } + seen = {0: -1} + res = cur = 0 + + for i in range(len(s)): + if s[i] in mapper: + cur ^= mapper.get(s[i]) + # 全部奇偶性都相同,相减一定都是偶数 + if cur in seen: + res = max(res, i - seen.get(cur)) + else: + seen[cur] = i + return res + +``` + +**复杂度分析** + +- 时间复杂度:$O(n)$。 +- 空间复杂度:$O(n)$ + +## 关键点解析 + +- 前缀和 +- 状态压缩 + +## 相关题目 + +- [掌握前缀表达式真的可以为所欲为!](https://lucifer.ren/blog/2020/01/09/1310.xor-queries-of-a-subarray/) diff --git a/problems/139.word-break.md b/problems/139.word-break.md new file mode 100644 index 0000000..142b3ba --- /dev/null +++ b/problems/139.word-break.md @@ -0,0 +1,143 @@ + +## 题目地址 + +https://leetcode.com/problems/word-break/description/ + +## 题目描述 + +``` +Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words. + +Note: + +The same word in the dictionary may be reused multiple times in the segmentation. +You may assume the dictionary does not contain duplicate words. +Example 1: + +Input: s = "leetcode", wordDict = ["leet", "code"] +Output: true +Explanation: Return true because "leetcode" can be segmented as "leet code". +Example 2: + +Input: s = "applepenapple", wordDict = ["apple", "pen"] +Output: true +Explanation: Return true because "applepenapple" can be segmented as "apple pen apple". + Note that you are allowed to reuse a dictionary word. +Example 3: + +Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] +Output: false + +``` + +## 思路 + +这道题是给定一个字典和一个句子,判断该句子是否可以由字典里面的单词组出来,一个单词可以用多次。 + +暴力的方法是无解的,复杂度极其高。 我们考虑其是否可以拆分为小问题来解决。 +对于问题`(s, wordDict)` 我们是否可以用(s', wordDict) 来解决。 其中s' 是s 的子序列, +当s'变成寻常(长度为0)的时候问题就解决了。 我们状态转移方程变成了这道题的难点。 + +我们可以建立一个数组dp, dp[i]代表 字符串 s.substring(0, i) 能否由字典里面的单词组成, +值得注意的是,这里我们无法建立dp[i] 和 dp[i - 1] 的关系, +我们可以建立的是dp[i - word.length] 和 dp[i] 的关系。 + +我们用图来感受一下: + +![139.word-break-1](../assets/problems/139.word-break-1.png) + + +没有明白也没有关系,我们分步骤解读一下: + +(以下的图左边都代表s,右边都是dict,灰色代表没有处理的字符,绿色代表匹配成功,红色代表匹配失败) + +![139.word-break-2](../assets/problems/139.word-break-2.png) + +![139.word-break-3](../assets/problems/139.word-break-3.png) + +![139.word-break-4](../assets/problems/139.word-break-4.png) + +![139.word-break-5](../assets/problems/139.word-break-5.png) + + +上面分步解释了算法的基本过程,下面我们感性认识下这道题,我把它比喻为 +你正在`往一个老式手电筒🔦中装电池` + +![139.word-break-6](../assets/problems/139.word-break-6.png) + +## 代码 + +```js +/* + * @lc app=leetcode id=139 lang=javascript + * + * [139] Word Break + * + * https://leetcode.com/problems/word-break/description/ + * + * algorithms + * Medium (34.45%) + * Total Accepted: 317.8K + * Total Submissions: 913.9K + * Testcase Example: '"leetcode"\n["leet","code"]' + * + * Given a non-empty string s and a dictionary wordDict containing a list of + * non-empty words, determine if s can be segmented into a space-separated + * sequence of one or more dictionary words. + * + * Note: + * + * + * The same word in the dictionary may be reused multiple times in the + * segmentation. + * You may assume the dictionary does not contain duplicate words. + * + * + * Example 1: + * + * + * Input: s = "leetcode", wordDict = ["leet", "code"] + * Output: true + * Explanation: Return true because "leetcode" can be segmented as "leet + * code". + * + * + * Example 2: + * + * + * Input: s = "applepenapple", wordDict = ["apple", "pen"] + * Output: true + * Explanation: Return true because "applepenapple" can be segmented as "apple + * pen apple". + * Note that you are allowed to reuse a dictionary word. + * + * + * Example 3: + * + * + * Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] + * Output: false + * + * + */ +/** + * @param {string} s + * @param {string[]} wordDict + * @return {boolean} + */ +var wordBreak = function(s, wordDict) { + const dp = Array(s.length + 1); + dp[0] = true; + for (let i = 0; i < s.length + 1; i++) { + for (let word of wordDict) { + if (dp[i - word.length] && word.length <= i) { + if (s.substring(i - word.length, i) === word) { + dp[i] = true; + } + } + } + } + + return dp[s.length] || false; +}; +``` diff --git a/problems/144.binary-tree-preorder-traversal.md b/problems/144.binary-tree-preorder-traversal.md new file mode 100644 index 0000000..12e92fb --- /dev/null +++ b/problems/144.binary-tree-preorder-traversal.md @@ -0,0 +1,156 @@ +## 题目地址 + +https://leetcode.com/problems/binary-tree-preorder-traversal/description/ + +## 题目描述 + +``` +Given a binary tree, return the preorder traversal of its nodes' values. + +Example: + +Input: [1,null,2,3] + 1 + \ + 2 + / + 3 + +Output: [1,2,3] +Follow up: Recursive solution is trivial, could you do it iteratively? + +``` + +## 思路 + +这道题目是前序遍历,这个和之前的`leetcode 94 号问题 - 中序遍历`完全不一回事。 + +前序遍历是`根左右`的顺序,注意是`根`开始,那么就很简单。直接先将根节点入栈,然后 +看有没有右节点,有则入栈,再看有没有左节点,有则入栈。 然后出栈一个元素,重复即可。 + +> 其他树的非递归遍历课没这么简单 + +## 关键点解析 + +- 二叉树的基本操作(遍历) + > 不同的遍历算法差异还是蛮大的 +- 如果非递归的话利用栈来简化操作 + +- 如果数据规模不大的话,建议使用递归 + +- 递归的问题需要注意两点,一个是终止条件,一个如何缩小规模 + +1. 终止条件,自然是当前这个元素是 null(链表也是一样) + +2. 由于二叉树本身就是一个递归结构, 每次处理一个子树其实就是缩小了规模, + 难点在于如何合并结果,这里的合并结果其实就是`mid.concat(left).concat(right)`, + mid 是一个具体的节点,left 和 right`递归求出即可` + +## 代码 + +- 语言支持:JS,C++ + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=144 lang=javascript + * + * [144] Binary Tree Preorder Traversal + * + * https://leetcode.com/problems/binary-tree-preorder-traversal/description/ + * + * algorithms + * Medium (50.36%) + * Total Accepted: 314K + * Total Submissions: 621.2K + * Testcase Example: '[1,null,2,3]' + * + * Given a binary tree, return the preorder traversal of its nodes' values. + * + * Example: + * + * + * Input: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * Output: [1,2,3] + * + * + * Follow up: Recursive solution is trivial, could you do it iteratively? + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var preorderTraversal = function(root) { + // 1. Recursive solution + + // if (!root) return []; + + // return [root.val].concat(preorderTraversal(root.left)).concat(preorderTraversal(root.right)); + + // 2. iterative solutuon + + if (!root) return []; + const ret = []; + const stack = [root]; + let t = stack.pop(); + + while (t) { + ret.push(t.val); + if (t.right) { + stack.push(t.right); + } + if (t.left) { + stack.push(t.left); + } + t = stack.pop(); + } + + return ret; +}; +``` + +C++ Code: + +```C++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +public: + vector preorderTraversal(TreeNode* root) { + vector v; + vector s; + while (root != NULL || !s.empty()) { + while (root != NULL) { + v.push_back(root->val); + s.push_back(root); + root = root->left; + } + root = s.back()->right; + s.pop_back(); + } + return v; + } +}; +``` diff --git a/problems/145.binary-tree-postorder-traversal.md b/problems/145.binary-tree-postorder-traversal.md new file mode 100644 index 0000000..0b20ea2 --- /dev/null +++ b/problems/145.binary-tree-postorder-traversal.md @@ -0,0 +1,135 @@ +## 题目地址 +https://leetcode.com/problems/binary-tree-postorder-traversal/description/ + +## 题目描述 + +``` +Given a binary tree, return the postorder traversal of its nodes' values. + +For example: +Given binary tree {1,#,2,3}, + + 1 + \ + 2 + / + 3 + + +return [3,2,1]. + +Note: Recursive solution is trivial, could you do it iteratively? + +``` + +## 思路 + +相比于前序遍历,后续遍历思维上难度要大些,前序遍历是通过一个stack,首先压入父亲结点,然后弹出父亲结点,并输出它的value,之后压人其右儿子,左儿子即可。 + +然而后序遍历结点的访问顺序是:左儿子 -> 右儿子 -> 自己。那么一个结点需要两种情况下才能够输出: +第一,它已经是叶子结点; +第二,它不是叶子结点,但是它的儿子已经输出过。 + +那么基于此我们只需要记录一下当前输出的结点即可。对于一个新的结点,如果它不是叶子结点,儿子也没有访问,那么就需要将它的右儿子,左儿子压入。 +如果它满足输出条件,则输出它,并记录下当前输出结点。输出在stack为空时结束。 + + +## 关键点解析 + +- 二叉树的基本操作(遍历) +> 不同的遍历算法差异还是蛮大的 +- 如果非递归的话利用栈来简化操作 + +- 如果数据规模不大的话,建议使用递归 + +- 递归的问题需要注意两点,一个是终止条件,一个如何缩小规模 + +1. 终止条件,自然是当前这个元素是null(链表也是一样) + +2. 由于二叉树本身就是一个递归结构, 每次处理一个子树其实就是缩小了规模, +难点在于如何合并结果,这里的合并结果其实就是`left.concat(right).concat(mid)`, +mid是一个具体的节点,left和right`递归求出即可` + + +## 代码 + +```js +/* + * @lc app=leetcode id=145 lang=javascript + * + * [145] Binary Tree Postorder Traversal + * + * https://leetcode.com/problems/binary-tree-postorder-traversal/description/ + * + * algorithms + * Hard (47.06%) + * Total Accepted: 242.6K + * Total Submissions: 512.8K + * Testcase Example: '[1,null,2,3]' + * + * Given a binary tree, return the postorder traversal of its nodes' values. + * + * Example: + * + * + * Input: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * Output: [3,2,1] + * + * + * Follow up: Recursive solution is trivial, could you do it iteratively? + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var postorderTraversal = function(root) { + // 1. Recursive solution + + // if (!root) return []; + + // return postorderTraversal(root.left).concat(postorderTraversal(root.right)).concat(root.val); + + // 2. iterative solutuon + + if (!root) return []; + const ret = []; + const stack = [root]; + let p = root; // 标识元素,用来判断节点是否应该出栈 + + while (stack.length > 0) { + const top = stack[stack.length - 1]; + if ( + top.left === p || + top.right === p || // 子节点已经遍历过了 + (top.left === null && top.right === null) // 叶子元素 + ) { + p = stack.pop(); + ret.push(p.val); + } else { + if (top.right) { + stack.push(top.right); + } + if (top.left) { + stack.push(top.left); + } + } + } + + return ret; +}; + +``` diff --git a/problems/146.lru-cache.md b/problems/146.lru-cache.md new file mode 100644 index 0000000..0abd272 --- /dev/null +++ b/problems/146.lru-cache.md @@ -0,0 +1,112 @@ +## 题目地址 +https://leetcode.com/problems/lru-cache/description/ + +## 题目描述 + +``` +Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put. + +get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. +put(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item. + +Follow up: +Could you do both operations in O(1) time complexity? + +Example: + +LRUCache cache = new LRUCache( 2 /* capacity */ ); + +cache.put(1, 1); +cache.put(2, 2); +cache.get(1); // returns 1 +cache.put(3, 3); // evicts key 2 +cache.get(2); // returns -1 (not found) +cache.put(4, 4); // evicts key 1 +cache.get(1); // returns -1 (not found) +cache.get(3); // returns 3 +cache.get(4); // returns 4 + +``` + +## 思路 + +`本题已被收录到我的新书中,敬请期待~` + +由于是保留是最近使用的 N 条数据,这就和队列的特性很符合, 先进入队列的,先出队列。 + +因此思路就是用一个队列来记录目前缓存的所有 key, 每次 push 都进行判断,如果 +超出最大容量限制则进行清除缓存的操作, 具体清除谁就按照刚才说的队列方式进行处理,同时对 key 进行入队操作。 + +get 的时候,如果缓存中有,则调整队列(具体操作为删除指定元素和入队两个操作)。 缓存中没有则返回-1 + +## 关键点解析 + +- 队列简化操作 + +- 队列的操作是这道题的灵魂, 很容易少考虑情况 + +## 代码 + +```js +/** + * @param {number} capacity + */ +var LRUCache = function(capacity) { + this.cache = {}; + this.capacity = capacity; + this.size = 0; + this.queue = []; +}; + +/** + * @param {number} key + * @return {number} + */ +LRUCache.prototype.get = function(key) { + const hit = this.cache[key]; + + if (hit !== undefined) { + this.queue = this.queue.filter(q => q !== key); + this.queue.push(key); + return hit; + } + return -1; +}; + +/** + * @param {number} key + * @param {number} value + * @return {void} + */ +LRUCache.prototype.put = function(key, value) { + const hit = this.cache[key]; + + // update cache + this.cache[key] = value; + + if (!hit) { + // invalid cache and resize size; + if (this.size === this.capacity) { + // invalid cache + const key = this.queue.shift(); + this.cache[key] = undefined; + } else { + this.size = this.size + 1; + } + this.queue.push(key); + } else { + this.queue = this.queue.filter(q => q !== key); + this.queue.push(key); + } +}; + +/** + * Your LRUCache object will be instantiated and called as such: + * var obj = new LRUCache(capacity) + * var param_1 = obj.get(key) + * obj.put(key,value) + */ + +``` + +本题删除的时间复杂度是不符合要求的。 应该采用双向链表在 O(1) 时间找到前驱进行删除。 diff --git a/problems/15.3-sum.md b/problems/15.3-sum.md new file mode 100644 index 0000000..c354bef --- /dev/null +++ b/problems/15.3-sum.md @@ -0,0 +1,125 @@ +## 题目地址 +https://leetcode.com/problems/3sum/description/ + +## 题目描述 +``` +Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero. + +Note: + +The solution set must not contain duplicate triplets. + +Example: + +Given array nums = [-1, 0, 1, 2, -1, -4], + +A solution set is: +[ + [-1, 0, 1], + [-1, -1, 2] +] + +``` +## 思路 + +我们采用`分治`的思想. 想要找出三个数相加等于0,我们可以数组依次遍历, +每一项a[i]我们都认为它是最终能够用组成0中的一个数字,那么我们的目标就是找到 +剩下的元素(除a[i])`两个`相加等于-a[i]. + +通过上面的思路,我们的问题转化为了`给定一个数组,找出其中两个相加等于给定值`, +这个问题是比较简单的, 我们只需要对数组进行排序,然后双指针解决即可。 加上我们需要外层遍历依次数组,因此总的时间复杂度应该是O(N^2)。 + +思路如图所示: + +![15.3-sum](../assets/problems/15.3-sum.png) + +> 在这里之所以要排序解决是因为, 我们算法的瓶颈在这里不在于排序,而在于O(N^2),如果我们瓶颈是排序,就可以考虑别的方式了 + + +> 如果找某一个特定元素,一个指针就够了。如果是找两个元素满足一定关系(比如求和等于特定值),需要双指针, +当然前提是数组有序。 +## 关键点解析 + +- 排序之后,用双指针 +- 分治 + +## 代码 +```js + +/* + * @lc app=leetcode id=15 lang=javascript + * + * [15] 3Sum + * + * https://leetcode.com/problems/3sum/description/ + * + * algorithms + * Medium (23.51%) + * Total Accepted: 531.5K + * Total Submissions: 2.2M + * Testcase Example: '[-1,0,1,2,-1,-4]' + * + * Given an array nums of n integers, are there elements a, b, c in nums such + * that a + b + c = 0? Find all unique triplets in the array which gives the + * sum of zero. + * + * Note: + * + * The solution set must not contain duplicate triplets. + * + * Example: + * + * + * Given array nums = [-1, 0, 1, 2, -1, -4], + * + * A solution set is: + * [ + * ⁠ [-1, 0, 1], + * ⁠ [-1, -1, 2] + * ] + * + * + */ +/** + * @param {number[]} nums + * @return {number[][]} + */ +var threeSum = function(nums) { + if (nums.length < 3) return []; + const list = []; + nums.sort((a, b) => a - b); + for (let i = 0; i < nums.length; i++) { + //nums is sorted,so it's impossible to have a sum = 0 + if (nums[i] > 0) break; + // skip duplicated result without set + if (i > 0 && nums[i] === nums[i - 1]) continue; + let left = i + 1; + let right = nums.length - 1; + + // for each index i + // we want to find the triplet [i, left, right] which sum to 0 + while (left < right) { + // since left < right, and left > i, no need to compare i === left and i === right. + if (nums[left] + nums[right] + nums[i] === 0) { + list.push([nums[left], nums[right], nums[i]]); + // skip duplicated result without set + while(nums[left] === nums[left + 1]) { + left++; + } + left++; + // skip duplicated result without set + while(nums[right] === nums[right - 1]) { + right--; + } + right--; + continue; + } else if (nums[left] + nums[right] + nums[i] > 0) { + right--; + } else { + left++; + } + } + } + return list; +}; +``` diff --git a/problems/150.evaluate-reverse-polish-notation.md b/problems/150.evaluate-reverse-polish-notation.md new file mode 100644 index 0000000..8b56c0d --- /dev/null +++ b/problems/150.evaluate-reverse-polish-notation.md @@ -0,0 +1,142 @@ + + +## 题目地址 +https://leetcode.com/problems/evaluate-reverse-polish-notation/description/ + +## 题目描述 + +``` +Evaluate the value of an arithmetic expression in Reverse Polish Notation. + +Valid operators are +, -, *, /. Each operand may be an integer or another expression. + +Note: + +Division between two integers should truncate toward zero. +The given RPN expression is always valid. That means the expression would always evaluate to a result and there won't be any divide by zero operation. +``` +## 思路 +逆波兰表达式又叫做后缀表达式。在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之间,这种表示法也称为`中缀表示`。 + +波兰逻辑学家J.Lukasiewicz于1929年提出了另一种表示表达式的方法,按此方法,每一运算符都置于其运算对象之后,故称为`后缀表示`。 + +> 逆波兰表达式是一种十分有用的表达式,它将复杂表达式转换为可以依靠简单的操作得到计算结果的表达式。例如(a+b)*(c+d)转换为ab+cd+* + + +## 关键点 + +1. 栈的基本用法 + +2. 如果你用的是JS的话,需要注意/ 和 其他很多语言是不一样的 + +3. 如果你用的是JS的话,需要先将字符串转化为数字。否则有很多意想不到的结果 + +4. 操作符的顺序应该是 先出栈的是第二位,后出栈的是第一位。 这在不符合交换律的操作中很重要, 比如减法和除法。 + +## 代码 + +```js +/* + * @lc app=leetcode id=150 lang=javascript + * + * [150] Evaluate Reverse Polish Notation + * + * https://leetcode.com/problems/evaluate-reverse-polish-notation/description/ + * + * algorithms + * Medium (31.43%) + * Total Accepted: 153.3K + * Total Submissions: 485.8K + * Testcase Example: '["2","1","+","3","*"]' + * + * Evaluate the value of an arithmetic expression in Reverse Polish Notation. + * + * Valid operators are +, -, *, /. Each operand may be an integer or another + * expression. + * + * Note: + * + * + * Division between two integers should truncate toward zero. + * The given RPN expression is always valid. That means the expression would + * always evaluate to a result and there won't be any divide by zero + * operation. + * + * + * Example 1: + * + * + * Input: ["2", "1", "+", "3", "*"] + * Output: 9 + * Explanation: ((2 + 1) * 3) = 9 + * + * + * Example 2: + * + * + * Input: ["4", "13", "5", "/", "+"] + * Output: 6 + * Explanation: (4 + (13 / 5)) = 6 + * + * + * Example 3: + * + * + * Input: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"] + * Output: 22 + * Explanation: + * ⁠ ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 + * = ((10 * (6 / (12 * -11))) + 17) + 5 + * = ((10 * (6 / -132)) + 17) + 5 + * = ((10 * 0) + 17) + 5 + * = (0 + 17) + 5 + * = 17 + 5 + * = 22 + * + * + */ +/** + * @param {string[]} tokens + * @return {number} + */ +var evalRPN = function(tokens) { + // 这种算法的前提是 tokens是有效的, + // 当然这由算法来保证 + const stack = []; + + for (let index = 0; index < tokens.length; index++) { + const token = tokens[index]; + // 对于运算数, 我们直接入栈 + if (!Number.isNaN(Number(token))) { + stack.push(token); + } else { + // 遇到操作符,我们直接大胆运算,不用考虑算术优先级 + // 然后将运算结果入栈即可 + + // 当然如果题目进一步扩展,允许使用单目等其他运算符,我们的算法需要做微小的调整 + const a = Number(stack.pop()); + const b = Number(stack.pop()); + if (token === "*") { + stack.push(b * a); + } else if (token === "/") { + stack.push(b / a >> 0); + } else if (token === "+") { + stack.push(b + a); + } else if (token === "-") { + stack.push(b - a); + } + } + } + + return stack.pop(); +}; + +``` + +## 扩展 + +逆波兰表达式中只改变运算符的顺序,并不会改变操作数的相对顺序,这是一个重要的性质。 +另外逆波兰表达式完全不关心操作符的优先级,这在中缀表达式中是做不到的,这很有趣,感兴趣的可以私下查找资料研究下为什么会这样。 + + + diff --git a/problems/152.maximum-product-subarray.md b/problems/152.maximum-product-subarray.md new file mode 100644 index 0000000..3ed9c07 --- /dev/null +++ b/problems/152.maximum-product-subarray.md @@ -0,0 +1,141 @@ +## 题目地址 + +https://leetcode.com/problems/maximum-product-subarray/description/ + +## 题目描述 +``` +给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 + +  + +示例 1: + +输入: [2,3,-2,4] +输出: 6 +解释: 子数组 [2,3] 有最大乘积 6。 +示例 2: + +输入: [-2,0,-1] +输出: 0 +解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 +``` + +## 思路 + +这道题目要我们求解连续的 n 个数中乘积最大的积是多少。这里提到了连续,笔者首先想到的就是滑动窗口,但是这里比较特殊,我们不能仅仅维护一个最大值,因此最小值(比如-20)乘以一个比较小的数(比如-10) +可能就会很大。 因此这种思路并不方便。 + +首先来暴力求解,我们使用两层循环来枚举所有可能项,这种解法的时间复杂度是O(n^2), 代码如下: + +```js +var maxProduct = function(nums) { + let max = nums[0]; + let temp = null; + for (let i = 0; i < nums.length; i++) { + temp = nums[i]; + for (let j = i + 1; j < nums.length; j++) { + temp *= nums[j]; + max = Math.max(temp, max); + } + } + + return max; +}; +``` + + + +前面说了`最小值(比如-20)乘以一个比较小的数(比如-10)可能就会很大` 。因此我们需要同时记录乘积最大值和乘积最小值,然后比较元素和这两个的乘积,去不断更新最大值。当然,我们也可以选择只取当前元素。因此实际上我们的选择有三种,而如何选择就取决于哪个选择带来的价值最大(乘积最大或者最小)。 + +![](https://pic.leetcode-cn.com/7d39989d10d982d44cbd6b6f693cf5171865c0654f7c3754e27ec1afc2c0de5d.jpg) + +这种思路的解法由于只需要遍历一次,其时间复杂度是O(n),代码见下方代码区。 + +## 关键点 + +- 同时记录乘积最大值和乘积最小值 +## 代码 + +代码支持:Python3,JavaScript + + + +Python3 Code: + + +```python + + +class Solution: + def maxProduct(self, nums: List[int]) -> int: + n = len(nums) + max__dp = [1] * (n + 1) + min_dp = [1] * (n + 1) + ans = float('-inf') + + for i in range(1, n + 1): + max__dp[i] = max(max__dp[i - 1] * nums[i - 1], + min_dp[i - 1] * nums[i - 1], nums[i - 1]) + min_dp[i] = min(max__dp[i - 1] * nums[i - 1], + min_dp[i - 1] * nums[i - 1], nums[i - 1]) + ans = max(ans, max__dp[i]) + return ans + ``` + + +**复杂度分析** +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(N)$ + + +当我们知道动态转移方程的时候,其实应该发现了。我们的dp[i] 只和 dp[i - 1]有关,这是一个空间优化的信号,告诉我们`可以借助两个额外变量记录即可`。 + + +Python3 Code: + + +```python + +class Solution: + def maxProduct(self, nums: List[int]) -> int: + n = len(nums) + a = b = 1 + ans = float('-inf') + + for i in range(1, n + 1): + temp = a + a = max(a * nums[i - 1], + b * nums[i - 1], nums[i - 1]) + b = min(temp * nums[i - 1], + b * nums[i - 1], nums[i - 1]) + ans = max(ans, a) + return ans + +``` + +JavaScript Code: + +```js +var maxProduct = function(nums) { + let max = nums[0]; + let min = nums[0]; + let res = nums[0]; + + for (let i = 1; i < nums.length; i++) { + let tmp = min; + min = Math.min(nums[i], Math.min(max * nums[i], min * nums[i])); // 取最小 + max = Math.max(nums[i], Math.max(max * nums[i], tmp * nums[i])); /// 取最大 + res = Math.max(res, max); + } + return res; +}; +``` + + +**复杂度分析** +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(1)$ + +更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经30K star啦。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 diff --git a/problems/155.min-stack.md b/problems/155.min-stack.md new file mode 100644 index 0000000..ab07da5 --- /dev/null +++ b/problems/155.min-stack.md @@ -0,0 +1,304 @@ +# 题目地址 + +https://leetcode.com/problems/min-stack/description/ + +# 题目描述 + +``` +Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. + +push(x) -- Push element x onto stack. +pop() -- Removes the element on top of the stack. +top() -- Get the top element. +getMin() -- Retrieve the minimum element in the stack. +Example: +MinStack minStack = new MinStack(); +minStack.push(-2); +minStack.push(0); +minStack.push(-3); +minStack.getMin(); --> Returns -3. +minStack.pop(); +minStack.top(); --> Returns 0. +minStack.getMin(); --> Returns -2. + +``` + +# 差值法 + +## 思路 + +符合直觉的方法是,每次对栈进行修改操作(push和pop)的时候更新最小值。 然后getMin只需要返回我们计算的最小值即可, +top也是直接返回栈顶元素即可。 这种做法每次修改栈都需要更新最小值,因此时间复杂度是O(n). + +![](https://pic.leetcode-cn.com/7beed41b8dc0325445721a36b7db34e1af902441b67996d2eeadcb1f5a5e33d9.jpg) + +是否有更高效的算法呢?答案是有的。 + +我们每次入栈的时候,保存的不再是真正的数字,而是它与当前最小值的差(当前元素没有入栈的时候的最小值)。 +这样我们pop和top的时候拿到栈顶元素再加上**上一个**最小值即可。 +另外我们在push和pop的时候去更新min,这样getMin的时候就简单了,直接返回min。 + +> 注意上面加粗的“上一个”,不是“当前的最小值” + +经过上面的分析,问题的关键转化为“如何求得上一个最小值”,解决这个的关键点在于利用min。 + +pop或者top的时候: + +- 如果栈顶元素小于0,说明栈顶是当前最小的元素,它出栈会对min造成影响,我们需要去更新min。 +上一个最小的是“min - 栈顶元素”,我们需要将上一个最小值更新为当前的最小值 + + > 因为栈顶元素入栈的时候的通过 `栈顶元素 = 真实值 - 上一个最小的元素` 得到的, + 而真实值 = min, 因此可以得出`上一个最小的元素 = 真实值 -栈顶元素` + +- 如果栈顶元素大于0,说明它对最小值`没有影响`,上一个最小值就是上上个最小值。 + +![](https://pic.leetcode-cn.com/7da0473d92d70bb47ce7b62303c062e5f517b09d1bf501c4ad341b65415d5c43.jpg) +![](https://pic.leetcode-cn.com/aefec54238c942c484837ea6c724304fb179d3d64f110481d955d9eea65c4fc5.jpg) + +## 关键点 + +- 最小栈存储的不应该是真实值,而是真实值和min的差值 +- top的时候涉及到对数据的还原,这里千万注意是**上一个**最小值 + +## 代码 + +* 语言支持:JS,Python + +Javascript Code: + +```js +/* + * @lc app=leetcode id=155 lang=javascript + * + * [155] Min Stack + */ +/** + * initialize your data structure here. + */ +var MinStack = function() { + this.stack = []; + this.minV = Number.MAX_VALUE; +}; + +/** + * @param {number} x + * @return {void} + */ +MinStack.prototype.push = function(x) { + // update 'min' + const minV = this.minV; + if (x < this.minV) { + this.minV = x; + } + return this.stack.push(x - minV); +}; + +/** + * @return {void} + */ +MinStack.prototype.pop = function() { + const item = this.stack.pop(); + const minV = this.minV; + + if (item < 0) { + this.minV = minV - item; + return minV; + } + return item + minV; +}; + +/** + * @return {number} + */ +MinStack.prototype.top = function() { + const item = this.stack[this.stack.length - 1]; + const minV = this.minV; + + if (item < 0) { + return minV; + } + return item + minV; +}; + +/** + * @return {number} + */ +MinStack.prototype.min = function() { + return this.minV; +}; + +/** + * Your MinStack object will be instantiated and called as such: + * var obj = new MinStack() + * obj.push(x) + * obj.pop() + * var param_3 = obj.top() + * var param_4 = obj.min() + */ +``` + +Python Code: + +```python +class MinStack: + + def __init__(self): + """ + initialize your data structure here. + """ + self.minV = float('inf') + self.stack = [] + + def push(self, x: int) -> None: + self.stack.append(x - self.minV) + if x < self.minV: + self.minV = x + + def pop(self) -> None: + if not self.stack: + return + tmp = self.stack.pop() + if tmp < 0: + self.minV -= tmp + + def top(self) -> int: + if not self.stack: + return + tmp = self.stack[-1] + if tmp < 0: + return self.minV + else: + return self.minV + tmp + + def min(self) -> int: + return self.minV + + + +# Your MinStack object will be instantiated and called as such: +# obj = MinStack() +# obj.push(x) +# obj.pop() +# param_3 = obj.top() +# param_4 = obj.min() +``` + +**复杂度分析** +- 时间复杂度:O(1) +- 空间复杂度:O(1) + + +# 两个栈 + +## 思路 + +我们使用两个栈: + +- 一个栈存放全部的元素,push,pop都是正常操作这个正常栈。 +- 另一个存放最小栈。 每次push,如果比最小栈的栈顶还小,我们就push进最小栈,否则不操作 +- 每次pop的时候,我们都判断其是否和最小栈栈顶元素相同,如果相同,那么我们pop掉最小栈的栈顶元素即可 + +## 关键点 + +- 往minstack中 push的判断条件。 应该是stack为空或者x小于等于minstack栈顶元素 + + +## 代码 + +JavaScript: + +```js +/** + * initialize your data structure here. + */ +var MinStack = function() { + this.stack = [] + this.minStack = [] +}; + +/** + * @param {number} x + * @return {void} + */ +MinStack.prototype.push = function(x) { + this.stack.push(x) + if (this.minStack.length == 0 || x <= this.minStack[this.minStack.length - 1]) { + this.minStack.push(x) + } +}; + +/** + * @return {void} + */ +MinStack.prototype.pop = function() { + const x = this.stack.pop() + if (x !== void 0 && x === this.minStack[this.minStack.length - 1]) { + this.minStack.pop() + } +}; + +/** + * @return {number} + */ +MinStack.prototype.top = function() { + return this.stack[this.stack.length - 1] +}; + +/** + * @return {number} + */ +MinStack.prototype.min = function() { + return this.minStack[this.minStack.length - 1] +}; + +/** + * Your MinStack object will be instantiated and called as such: + * var obj = new MinStack() + * obj.push(x) + * obj.pop() + * var param_3 = obj.top() + * var param_4 = obj.min() + */ +``` + + +Python3: + +```python +class MinStack: + + def __init__(self): + """ + initialize your data structure here. + """ + self.stack = [] + self.minstack = [] + + def push(self, x: int) -> None: + self.stack.append(x) + if not self.minstack or x <= self.minstack[-1]: + self.minstack.append(x) + + def pop(self) -> None: + tmp = self.stack.pop() + if tmp == self.minstack[-1]: + self.minstack.pop() + + def top(self) -> int: + return self.stack[-1] + + def min(self) -> int: + return self.minstack[-1] + + +# Your MinStack object will be instantiated and called as such: +# obj = MinStack() +# obj.push(x) +# obj.pop() +# param_3 = obj.top() +# param_4 = obj.min() +``` + +**复杂度分析** +- 时间复杂度:O(1) +- 空间复杂度:O(N) diff --git a/problems/167.two-sum-ii-input-array-is-sorted.md b/problems/167.two-sum-ii-input-array-is-sorted.md new file mode 100644 index 0000000..9453655 --- /dev/null +++ b/problems/167.two-sum-ii-input-array-is-sorted.md @@ -0,0 +1,125 @@ + +## 题目地址 +https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/ + +## 题目描述 + +这是leetcode头号题目`two sum`的第二个版本,难度简单。 + +``` +Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number. + +The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. + +Note: + +Your returned answers (both index1 and index2) are not zero-based. +You may assume that each input would have exactly one solution and you may not use the same element twice. +Example: + +Input: numbers = [2,7,11,15], target = 9 +Output: [1,2] +Explanation: The sum of 2 and 7 is 9. Therefore index1 = 1, index2 = 2. + +``` + +## 思路 + +由于题目没有对空间复杂度有求,用一个hashmap 存储已经访问过的数字即可。 + +假如题目空间复杂度有要求,由于数组是有序的,只需要双指针即可。一个left指针,一个right指针, +如果left + right 值 大于target 则 right左移动, 否则left右移,代码见下方python code。 + +> 如果数组无序,需要先排序(从这里也可以看出排序是多么重要的操作) + + +## 关键点解析 + +无 + + +## 代码 + +* 语言支持:JS,Python + +Javascript Code: + +```js +/* + * @lc app=leetcode id=167 lang=javascript + * + * [167] Two Sum II - Input array is sorted + * + * https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/ + * + * algorithms + * Easy (49.46%) + * Total Accepted: 221.8K + * Total Submissions: 447K + * Testcase Example: '[2,7,11,15]\n9' + * + * Given an array of integers that is already sorted in ascending order, find + * two numbers such that they add up to a specific target number. + * + * The function twoSum should return indices of the two numbers such that they + * add up to the target, where index1 must be less than index2. + * + * Note: + * + * + * Your returned answers (both index1 and index2) are not zero-based. + * You may assume that each input would have exactly one solution and you may + * not use the same element twice. + * + * + * Example: + * + * + * Input: numbers = [2,7,11,15], target = 9 + * Output: [1,2] + * Explanation: The sum of 2 and 7 is 9. Therefore index1 = 1, index2 = 2. + * + */ +/** + * @param {number[]} numbers + * @param {number} target + * @return {number[]} + */ +var twoSum = function(numbers, target) { + const visited = {} // 记录出现的数字, 空间复杂度N + + for (let index = 0; index < numbers.length; index++) { + const element = numbers[index]; + if (visited[target - element] !== void 0) { + return [visited[target - element], index + 1] + } + visited[element] = index + 1; + } + return []; +}; +``` + +Python Code: + +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + visited = {} + for index, number in enumerate(numbers): + if target - number in visited: + return [visited[target-number], index+1] + else: + visited[number] = index + 1 + +# 双指针思路实现 +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + left, right = 0, len(numbers) - 1 + while left < right: + if numbers[left] + numbers[right] < target: + left += 1 + if numbers[left] + numbers[right] > target: + right -= 1 + if numbers[left] + numbers[right] == target: + return [left+1, right+1] +``` diff --git a/problems/169.majority-element.md b/problems/169.majority-element.md new file mode 100644 index 0000000..e96aa91 --- /dev/null +++ b/problems/169.majority-element.md @@ -0,0 +1,84 @@ + +## 题目地址 +https://leetcode.com/problems/majority-element/description/ + +## 题目描述 + +``` +Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times. + +You may assume that the array is non-empty and the majority element always exist in the array. + +Example 1: + +Input: [3,2,3] +Output: 3 +Example 2: + +Input: [2,2,1,1,1,2,2] +Output: 2 + +``` + +## 思路 + +符合直觉的做法是利用额外的空间去记录每个元素出现的次数,并用一个单独的变量记录当前出现次数最多的元素。 + +但是这种做法空间复杂度较高,有没有可能进行优化呢? 答案就是用"投票算法"。 + +投票算法的原理是通过不断消除不同元素直到没有不同元素,剩下的元素就是我们要找的元素。 + +![](https://tva1.sinaimg.cn/large/0082zybply1gbv38hcpf2j30mz0cjjt4.jpg) + +## 关键点解析 + +- 投票算法 + + +## 代码 + +* 语言支持:JS,Python + +Javascript Code: + +```js +var majorityElement = function(nums) { + let count = 1; + let majority = nums[0]; + for(let i = 1; i < nums.length; i++) { + if (count === 0) { + majority = nums[i]; + } + if (nums[i] === majority) { + count ++; + } else { + count --; + } + } + return majority; +}; +``` + +Python Code: + +```python +class Solution: + def majorityElement(self, nums: List[int]) -> int: + count, majority = 1, nums[0] + for num in nums[1:]: + if count == 0: + majority = num + if num == majority: + count += 1 + else: + count -= 1 + return majority +``` + +**复杂度分析** +- 时间复杂度:$O(N)$,其中N为数组长度 +- 空间复杂度:$O(1)$ + +欢迎关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) diff --git a/problems/17.Letter-Combinations-of-a-Phone-Number.md b/problems/17.Letter-Combinations-of-a-Phone-Number.md new file mode 100644 index 0000000..3845eb7 --- /dev/null +++ b/problems/17.Letter-Combinations-of-a-Phone-Number.md @@ -0,0 +1,77 @@ +## 题目地址 +https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number + +## 题目描述 +``` +给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 + +给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 + +示例: + +输入:"23" +输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. + +说明: +尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。 + +``` + +## 思路 +使用回溯法进行求解,回溯是一种通过穷举所有可能情况来找到所有解的算法。如果一个候选解最后被发现并不是可行解,回溯算法会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。究其本质,其实就是枚举。 + +如果没有更多的数字需要被输入,说明当前的组合已经产生。 + +如果还有数字需要被输入: +- 遍历下一个数字所对应的所有映射的字母 +- 将当前的字母添加到组合最后,也就是 str + tmp[r] + +## 关键点 +利用回溯思想解题,在for循环中调用递归。 + +## 代码 +* 语言支持:JS + +```js +/** + * @param {string} digits + * @return {string[]} + */ +const letterCombinations = function (digits) { + if (!digits) { + return []; + } + const len = digits.length; + const map = new Map(); + map.set('2', 'abc'); + map.set('3', 'def'); + map.set('4', 'ghi'); + map.set('5', 'jkl'); + map.set('6', 'mno'); + map.set('7', 'pqrs'); + map.set('8', 'tuv'); + map.set('9', 'wxyz'); + const result = []; + + function generate(i, str) { + if (i == len) { + result.push(str); + return; + } + const tmp = map.get(digits[i]); + for (let r = 0; r < tmp.length; r++) { + generate(i + 1, str + tmp[r]); + } + } + generate(0, ''); + return result; +}; +``` + +***复杂度分析*** + +N + M 是输入数字的总数 + +- 时间复杂度:O(3^N * 4^M) +- 空间复杂度:O(3^N * 4^M) + diff --git a/problems/172.factorial-trailing-zeroes.md b/problems/172.factorial-trailing-zeroes.md new file mode 100644 index 0000000..59b25d8 --- /dev/null +++ b/problems/172.factorial-trailing-zeroes.md @@ -0,0 +1,96 @@ + +## 题目地址 +https://leetcode.com/problems/factorial-trailing-zeroes/description/ + +## 题目描述 + +``` +Given an integer n, return the number of trailing zeroes in n!. + +Example 1: + +Input: 3 +Output: 0 +Explanation: 3! = 6, no trailing zero. +Example 2: + +Input: 5 +Output: 1 +Explanation: 5! = 120, one trailing zero. +Note: Your solution should be in logarithmic time complexity. + +``` + +## 思路 + +我们需要求解这n个数字相乘的结果末尾有多少个0,由于题目要求log的复杂度,因此暴力求解是不行的。 + +通过观察,我们发现如果想要结果末尾是0,必须是分解质因数之后,2 和 5 相乘才行,同时因数分解之后发现5的个数远小于2, +因此我们只需要求解这n数字分解质因数之后一共有多少个5即可. + +![172.factorial-trailing-zeroes-2](../assets/problems/172.factorial-trailing-zeroes-2.png) + +如图如果n为30,那么结果应该是图中红色5的个数,即7。 + +![172.factorial-trailing-zeroes-1](../assets/problems/172.factorial-trailing-zeroes-1.png) + +我们的结果并不是直接f(n) = n / 5, 比如n为30, 25中是有两个5的。 +类似,n为150,会有7个这样的数字,通过观察我们发现规律`f(n) = n/5 + n/5^2 + n/5^3 + n/5^4 + n/5^5+..` + +![172.factorial-trailing-zeroes-3](../assets/problems/172.factorial-trailing-zeroes-3.png) + +如果可以发现上面的规律,用递归还是循环实现这个算式就看你的了。 +## 关键点解析 + +- 数论 + +## 代码 + +* 语言支持:JS,Python + +Javascript Code: + +```js +/* + * @lc app=leetcode id=172 lang=javascript + * + * [172] Factorial Trailing Zeroes + */ +/** + * @param {number} n + * @return {number} + */ +var trailingZeroes = function(n) { + // tag: 数论 + + // if (n === 0) return n; + + // 递归: f(n) = n / 5 + f(n / 5) + // return Math.floor(n / 5) + trailingZeroes(Math.floor(n / 5)); + let count = 0; + while (n >= 5) { + count += Math.floor(n / 5); + n = Math.floor(n / 5); + } + return count; +}; +``` + +Python Code: + +```python +class Solution: + def trailingZeroes(self, n: int) -> int: + count = 0 + while n >= 5: + n = n // 5 + count += n + return count + + +# 递归 +class Solution: + def trailingZeroes(self, n: int) -> int: + if n == 0: return 0 + return n // 5 + self.trailingZeroes(n // 5) +``` diff --git a/problems/19.removeNthNodeFromEndofList.md b/problems/19.removeNthNodeFromEndofList.md new file mode 100644 index 0000000..26d7aa0 --- /dev/null +++ b/problems/19.removeNthNodeFromEndofList.md @@ -0,0 +1,168 @@ +## 题目地址 +https://leetcode.com/problems/remove-nth-node-from-end-of-list/description + +## 题目描述 +Given a linked list, remove the n-th node from the end of list and return its head. + +Example: + +Given linked list: 1->2->3->4->5, and n = 2. + +After removing the second node from the end, the linked list becomes 1->2->3->5. +Note: + +Given n will always be valid. + +Follow up: + +Could you do this in one pass? + +## 思路 + +双指针,指针A先移动n次, 指针B再开始移动。当A到达null的时候, 指针b的位置正好是倒数n + +我们可以设想假设设定了双指针p和q的话,当q指向末尾的NULL,p与q之间相隔的元素个数为n时,那么删除掉p的下一个指针就完成了要求。 + +设置虚拟节点dummyHead指向head + +设定双指针p和q,初始都指向虚拟节点dummyHead + +移动q,直到p与q之间相隔的元素个数为n + +同时移动p与q,直到q指向的为NULL + +将p的下一个节点指向下下个节点 + + + +![19.removeNthNodeFromEndOfList](../assets/19.removeNthNodeFromEndOfList.gif) + +(图片来自: https://github.com/MisterBooo/LeetCodeAnimation) + +## 关键点解析 + +1. 链表这种数据结构的特点和使用 + +2. 使用双指针 + +3. 使用一个dummyHead简化操作 + +## 代码 + +Support: JS, Java + +- Javascript Implementation + +```js +/* + * @lc app=leetcode id=19 lang=javascript + * + * [19] Remove Nth Node From End of List + * + * https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/ + * + * algorithms + * Medium (34.03%) + * Total Accepted: 360.1K + * Total Submissions: 1.1M + * Testcase Example: '[1,2,3,4,5]\n2' + * + * Given a linked list, remove the n-th node from the end of list and return + * its head. + * + * Example: + * + * + * Given linked list: 1->2->3->4->5, and n = 2. + * + * After removing the second node from the end, the linked list becomes + * 1->2->3->5. + * + * + * Note: + * + * Given n will always be valid. + * + * Follow up: + * + * Could you do this in one pass? + * + */ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} head + * @param {number} n + * @return {ListNode} + */ +var removeNthFromEnd = function(head, n) { + let i = -1; + const noop = { + next: null + }; + + const dummyHead = new ListNode(); // 增加一个dummyHead 简化操作 + dummyHead.next = head; + + let currentP1 = dummyHead; + let currentP2 = dummyHead; + + + while (currentP1) { + + if (i === n) { + currentP2 = currentP2.next; + } + + if (i !== n) { + i++; + } + + currentP1 = currentP1.next; + } + + currentP2.next = ((currentP2 || noop).next || noop).next; + + return dummyHead.next; +}; + +``` + +- Java Code + +```java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { val = x; } + * } + */ +class Solution { + public ListNode removeNthFromEnd(ListNode head, int n) { + TreeNode dummy = new TreeNode(0); + dummy.next = head; + TreeNode first = dummy; + TreeNode second = dummy; + + if (int i=0; i<=n; i++) { + first = first.next; + } + + while (first != null) { + first = first.next; + second = second.next; + } + + second.next = second.next.next; + + return dummy.next; + } +} +``` \ No newline at end of file diff --git a/problems/190.reverse-bits.md b/problems/190.reverse-bits.md new file mode 100644 index 0000000..768ff8c --- /dev/null +++ b/problems/190.reverse-bits.md @@ -0,0 +1,188 @@ +## 题目地址 +https://leetcode.com/problems/reverse-bits/description/ + +## 题目描述 + +``` +Reverse bits of a given 32 bits unsigned integer. + + + +Example 1: + +Input: 00000010100101000001111010011100 +Output: 00111001011110000010100101000000 +Explanation: The input binary string 00000010100101000001111010011100 represents the unsigned integer 43261596, so return 964176192 which its binary representation is 00111001011110000010100101000000. +Example 2: + +Input: 11111111111111111111111111111101 +Output: 10111111111111111111111111111111 +Explanation: The input binary string 11111111111111111111111111111101 represents the unsigned integer 4294967293, so return 3221225471 which its binary representation is 10101111110010110010011101101001. + + +Note: + +Note that in some languages such as Java, there is no unsigned integer type. In this case, both input and output will be given as signed integer type and should not affect your implementation, as the internal binary representation of the integer is the same whether it is signed or unsigned. +In Java, the compiler represents the signed integers using 2's complement notation. Therefore, in Example 2 above the input represents the signed integer -3 and the output represents the signed integer -1073741825. + +``` + +## 思路 + +这道题是给定一个32位的无符号整型,让你按位翻转, 第一位变成最后一位, 第二位变成倒数第二位。。。 + +那么思路就是`双指针` + +> 这个指针可以加引号 + +- n从高位开始逐步左, res从低位(0)开始逐步右移 +- 逐步判断,如果该位是1,就res + 1 , 如果是该位是0, 就res + 0 +- 32位全部遍历完,则遍历结束 + + +## 关键点解析 + +1. 可以用任何数字和1进行位运算的结果都取决于该数字最后一位的特性简化操作和提高性能 + +eg : + +- n & 1 === 1, 说明n的最后一位是1 +- n & 1 === 0, 说明n的最后一位是0 + +2. 对于JS,ES规范在之前很多版本都是没有无符号整形的, 转化为无符号,可以用一个trick`n >>> 0 ` + +3. 双"指针" 模型 + +4. bit 运算 + + +## 代码 + +* 语言支持:JS,C++,Python + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=190 lang=javascript + * + * [190] Reverse Bits + * + * https://leetcode.com/problems/reverse-bits/description/ + * + * algorithms + * Easy (30.30%) + * Total Accepted: 173.7K + * Total Submissions: 568.2K + * Testcase Example: '00000010100101000001111010011100' + * + * Reverse bits of a given 32 bits unsigned integer. + * + * + * + * Example 1: + * + * + * Input: 00000010100101000001111010011100 + * Output: 00111001011110000010100101000000 + * Explanation: The input binary string 00000010100101000001111010011100 + * represents the unsigned integer 43261596, so return 964176192 which its + * binary representation is 00111001011110000010100101000000. + * + * + * Example 2: + * + * + * Input: 11111111111111111111111111111101 + * Output: 10111111111111111111111111111111 + * Explanation: The input binary string 11111111111111111111111111111101 + * represents the unsigned integer 4294967293, so return 3221225471 which its + * binary representation is 10101111110010110010011101101001. + * + * + * + * Note: + * + * + * Note that in some languages such as Java, there is no unsigned integer type. + * In this case, both input and output will be given as signed integer type and + * should not affect your implementation, as the internal binary representation + * of the integer is the same whether it is signed or unsigned. + * In Java, the compiler represents the signed integers using 2's complement + * notation. Therefore, in Example 2 above the input represents the signed + * integer -3 and the output represents the signed integer -1073741825. + * + * + * + * + * Follow up: + * + * If this function is called many times, how would you optimize it? + * + */ +/** + * @param {number} n - a positive integer + * @return {number} - a positive integer + */ +var reverseBits = function(n) { + let res = 0; + for (let i = 0; i < 32; i++) { + res = (res << 1) + (n & 1); + n = n >>> 1; + } + + return res >>> 0; +}; +``` + +C++ Code: + +```C++ +class Solution { +public: + uint32_t reverseBits(uint32_t n) { + auto ret = 0; + for (auto i = 0; i < 32; ++i) { + ret = (ret << 1) + (n & 1); + n >>= 1; + } + return ret; + } +}; +``` + +Python Code: + +```python +class Solution: + # @param n, an integer + # @return an integer + def reverseBits(self, n): + result = 0 + for i in range(32): + result = (result << 1) + (n & 1) + n >>= 1 + return result +``` + +## 拓展 +不使用迭代也可以完成相同的操作: +1. 两两相邻的1位对调 +2. 两两相邻的2位对调 +3. 两两相邻的4位对调 +4. 两两相邻的8位对调 +5. 两两相邻的16位对调 + +C++代码如下: +```C++ +class Solution { +public: + uint32_t reverseBits(uint32_t n) { + auto ret = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1); + ret = ((ret & 0xcccccccc) >> 2) | ((ret & 0x33333333) << 2); + ret = ((ret & 0xf0f0f0f0) >> 4) | ((ret & 0x0f0f0f0f) << 4); + ret = ((ret & 0xff00ff00) >> 8) | ((ret & 0x00ff00ff) << 8); + return ((ret & 0xffff0000) >> 16) | ((ret & 0x0000ffff) << 16); + } +}; +``` diff --git a/problems/191.number-of-1-bits.md b/problems/191.number-of-1-bits.md new file mode 100644 index 0000000..b4f8bc3 --- /dev/null +++ b/problems/191.number-of-1-bits.md @@ -0,0 +1,141 @@ +## 题目地址 +https://leetcode.com/problems/number-of-1-bits/description/ + +## 题目描述 + +``` +Write a function that takes an unsigned integer and return the number of '1' bits it has (also known as the Hamming weight). + + + +Example 1: + +Input: 00000000000000000000000000001011 +Output: 3 +Explanation: The input binary string 00000000000000000000000000001011 has a total of three '1' bits. +Example 2: + +Input: 00000000000000000000000010000000 +Output: 1 +Explanation: The input binary string 00000000000000000000000010000000 has a total of one '1' bit. +Example 3: + +Input: 11111111111111111111111111111101 +Output: 31 +Explanation: The input binary string 11111111111111111111111111111101 has a total of thirty one '1' bits. + + +Note: + +Note that in some languages such as Java, there is no unsigned integer type. In this case, the input will be given as signed integer type and should not affect your implementation, as the internal binary representation of the integer is the same whether it is signed or unsigned. +In Java, the compiler represents the signed integers using 2's complement notation. Therefore, in Example 3 above the input represents the signed integer -3. + +``` + +## 思路 + +这个题目的大意是: 给定一个无符号的整数, 返回其用二进制表式的时候的1的个数。 + +这里用一个trick, 可以轻松求出。 就是`n & (n - 1)` 可以`消除` n 最后的一个1的原理。 + +> 为什么能消除最后一个1, 其实也比较简单,大家自己想一下 + +这样我们可以不断进行`n = n & (n - 1)`直到n === 0 , 说明没有一个1了。 +这个时候`我们消除了多少1变成一个1都没有了, 就说明n有多少个1了`。 + +## 关键点解析 + +1. `n & (n - 1)` 可以`消除` n 最后的一个1的原理 简化操作 + +2. bit 运算 + + +## 代码 + +语言支持:JS, C++,Python + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=191 lang=javascript + * + */ +/** + * @param {number} n - a positive integer + * @return {number} + */ +var hammingWeight = function(n) { + let count = 0; + while (n !== 0) { + n = n & (n - 1); + count++; + } + + return count; +}; + +``` + +C++ code: + +```c++ +class Solution { +public: + int hammingWeight(uint32_t v) { + auto count = 0; + while (v != 0) { + v &= (v - 1); + ++count; + } + return count; + } +}; +``` + +Python Code: + +```python +class Solution(object): + def hammingWeight(self, n): + """ + :type n: int + :rtype: int + """ + count = 0 + while n: + n &= n - 1 + count += 1 + return count +``` + +## 扩展 +可以使用位操作来达到目的。例如8位的整数21: + +![number-of-1-bits](../assets/problems/191.number-of-1-bits.png) + +C++ Code: +```c++ +const uint32_t ODD_BIT_MASK = 0xAAAAAAAA; +const uint32_t EVEN_BIT_MASK = 0x55555555; +const uint32_t ODD_2BIT_MASK = 0xCCCCCCCC; +const uint32_t EVEN_2BIT_MASK = 0x33333333; +const uint32_t ODD_4BIT_MASK = 0xF0F0F0F0; +const uint32_t EVEN_4BIT_MASK = 0x0F0F0F0F; +const uint32_t ODD_8BIT_MASK = 0xFF00FF00; +const uint32_t EVEN_8BIT_MASK = 0x00FF00FF; +const uint32_t ODD_16BIT_MASK = 0xFFFF0000; +const uint32_t EVEN_16BIT_MASK = 0x0000FFFF; + +class Solution { +public: + + int hammingWeight(uint32_t v) { + v = (v & EVEN_BIT_MASK) + ((v & ODD_BIT_MASK) >> 1); + v = (v & EVEN_2BIT_MASK) + ((v & ODD_2BIT_MASK) >> 2); + v = (v & EVEN_4BIT_MASK) + ((v & ODD_4BIT_MASK) >> 4); + v = (v & EVEN_8BIT_MASK) + ((v & ODD_8BIT_MASK) >> 8); + return (v & EVEN_16BIT_MASK) + ((v & ODD_16BIT_MASK) >> 16); + } +}; +``` diff --git a/problems/198.house-robber.md b/problems/198.house-robber.md new file mode 100644 index 0000000..c294a2b --- /dev/null +++ b/problems/198.house-robber.md @@ -0,0 +1,186 @@ +## 题目地址 + +https://leetcode.com/problems/house-robber/description/ + +## 题目描述 + +``` +You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night. + +Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police. + +Example 1: + +Input: [1,2,3,1] +Output: 4 +Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3). + Total amount you can rob = 1 + 3 = 4. +Example 2: + +Input: [2,7,9,3,1] +Output: 12 +Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1). + Total amount you can rob = 2 + 9 + 1 = 12. + +``` + +## 思路 + +这是一道非常典型且简单的动态规划问题,但是在这里我希望通过这个例子, +让大家对动态规划问题有一点认识。 + +为什么别人的动态规划可以那么写,为什么没有用 dp 数组就搞定了。 +比如别人的爬楼梯问题怎么就用 fibnacci 搞定了?为什么?在这里我们就来看下。 + +思路还是和其他简单的动态规划问题一样,我们本质上在解决`对于第[i] 个房子,我们抢还是不抢。`的问题。 + +判断的标准就是总价值哪个更大, 那么对于抢的话`就是当前的房子可以抢的价值 + dp[i - 2]` + +> i - 1 不能抢,否则会触发警铃 + +如果不抢的话,就是`dp[i - 1]`. + +> 这里的 dp 其实就是`子问题`. + +状态转移方程也不难写`dp[i] = Math.max(dp[i - 2] + nums[i - 2], dp[i - 1]);`(注:这里为了方便计算,令 `dp[0]`和 `dp[1]`都等于 0,所以 `dp[i]`对应的是 `nums[i - 2]`) + +上述过程用图来表示的话,是这样的: + +![198.house-robber](../assets/problems/198.house-robber.png) + +我们仔细观察的话,其实我们只需要保证前一个 dp[i - 1] 和 dp[i - 2] 两个变量就好了, +比如我们计算到 i = 6 的时候,即需要计算 dp[6]的时候, 我们需要 dp[5], dp[4],但是我们 +不需要 dp[3], dp[2] ... + +因此代码可以简化为: + +```js +let a = 0; +let b = 0; + +for (let i = 0; i < nums.length; i++) { + const temp = b; + b = Math.max(a + nums[i], b); + a = temp; +} + +return b; +``` + +如上的代码,我们可以将空间复杂度进行优化,从 O(n)降低到 O(1), +类似的优化在 DP 问题中不在少数。 + +> 动态规划问题是递归问题查表,避免重复计算,从而节省时间。 +> 如果我们对问题加以分析和抽象,有可能对空间上进一步优化 + +## 关键点解析 + +## 代码 + +* 语言支持:JS,C++,Python + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=198 lang=javascript + * + * [198] House Robber + * + * https://leetcode.com/problems/house-robber/description/ + * + * algorithms + * Easy (40.80%) + * Total Accepted: 312.1K + * Total Submissions: 762.4K + * Testcase Example: '[1,2,3,1]' + * + * You are a professional robber planning to rob houses along a street. Each + * house has a certain amount of money stashed, the only constraint stopping + * you from robbing each of them is that adjacent houses have security system + * connected and it will automatically contact the police if two adjacent + * houses were broken into on the same night. + * + * Given a list of non-negative integers representing the amount of money of + * each house, determine the maximum amount of money you can rob tonight + * without alerting the police. + * + * Example 1: + * + * + * Input: [1,2,3,1] + * Output: 4 + * Explanation: Rob house 1 (money = 1) and then rob house 3 (money = + * 3). + * Total amount you can rob = 1 + 3 = 4. + * + * Example 2: + * + * + * Input: [2,7,9,3,1] + * Output: 12 + * Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house + * 5 (money = 1). + * Total amount you can rob = 2 + 9 + 1 = 12. + * + * + */ +/** + * @param {number[]} nums + * @return {number} + */ +var rob = function(nums) { + // Tag: DP + const dp = []; + dp[0] = 0; + dp[1] = 0; + + for (let i = 2; i < nums.length + 2; i++) { + dp[i] = Math.max(dp[i - 2] + nums[i - 2], dp[i - 1]); + } + + return dp[nums.length + 1]; +}; +``` + +C++ Code: + +> 与JavaScript代码略有差异,但状态迁移方程是一样的。 + +```C++ +class Solution { +public: + int rob(vector& nums) { + if (nums.empty()) return 0; + auto sz = nums.size(); + if (sz == 1) return nums[0]; + auto prev = nums[0]; + auto cur = max(prev, nums[1]); + for (auto i = 2; i < sz; ++i) { + auto tmp = cur; + cur = max(nums[i] + prev, cur); + prev = tmp; + } + return cur; + } +}; +``` + +Python Code: + +```python +class Solution: + def rob(self, nums: List[int]) -> int: + if not nums: + return 0 + + length = len(nums) + if length == 1: + return nums[0] + else: + prev = nums[0] + cur = max(prev, nums[1]) + for i in range(2, length): + cur, prev = max(prev + nums[i], cur), cur + return cur +``` diff --git a/problems/199.binary-tree-right-side-view.md b/problems/199.binary-tree-right-side-view.md new file mode 100644 index 0000000..40a844b --- /dev/null +++ b/problems/199.binary-tree-right-side-view.md @@ -0,0 +1,160 @@ +## 题目地址 + +https://leetcode.com/problems/binary-tree-right-side-view/description/ + +## 题目描述 + +``` +Given a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom. + +Example: + +Input: [1,2,3,null,5,null,4] +Output: [1, 3, 4] +Explanation: + + 1 <--- + / \ +2 3 <--- + \ \ + 5 4 <--- +``` + +## 思路 + +> 这道题和 leetcode 102 号问题《102.binary-tree-level-order-traversal》很像 + +这道题可以借助`队列`实现,首先把 root 入队,然后入队一个特殊元素 Null(来表示每层的结束)。 + +然后就是 while(queue.length), 每次处理一个节点,都将其子节点(在这里是 left 和 right)放到队列中。 + +然后不断的出队, 如果出队的是 null,则表式这一层已经结束了,我们就继续 push 一个 null。 + +## 关键点解析 + +- 队列 + +- 队列中用 Null(一个特殊元素)来划分每层 + +- 树的基本操作- 遍历 - 层次遍历(BFS) + +- 二叉树的右视图可以看作是层次遍历每次只取每一层的最右边的元素 + +## 代码 + +* 语言支持:JS,C++ + +Javascript Code: +```js +/* + * @lc app=leetcode id=199 lang=javascript + * + * [199] Binary Tree Right Side View + * + * https://leetcode.com/problems/binary-tree-right-side-view/description/ + * + * algorithms + * Medium (46.74%) + * Total Accepted: 156.1K + * Total Submissions: 332.3K + * Testcase Example: '[1,2,3,null,5,null,4]' + * + * Given a binary tree, imagine yourself standing on the right side of it, + * return the values of the nodes you can see ordered from top to bottom. + * + * Example: + * + * + * Input: [1,2,3,null,5,null,4] + * Output: [1, 3, 4] + * Explanation: + * + * ⁠ 1 <--- + * ⁠/ \ + * 2 3 <--- + * ⁠\ \ + * ⁠ 5 4 <--- + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var rightSideView = function(root) { + if (!root) return []; + + const ret = []; + const queue = [root, null]; + + let levelNodes = []; + + while (queue.length > 0) { + const node = queue.shift(); + if (node !== null) { + levelNodes.push(node.val); + if (node.right) { + queue.push(node.right); + } + if (node.left) { + queue.push(node.left); + } + } else { + // 一层遍历已经结束 + ret.push(levelNodes[0]); + if (queue.length > 0) { + queue.push(null); + } + levelNodes = []; + } + } + + return ret; +}; +``` +C++ Code: +```C++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +public: + vector rightSideView(TreeNode* root) { + auto ret = vector(); + if (root == nullptr) return ret; + auto q = queue(); + q.push(root); + while (!q.empty()) { + auto sz = q.size(); + for (auto i = 0; i < sz; ++i) { + auto n = q.front(); + q.pop(); + if (n->left != nullptr ) q.push(n->left); + if (n->right != nullptr ) q.push(n->right); + if (i == sz - 1) ret.push_back(n->val); + } + } + return ret; + } +}; +``` + +## 扩展 + +假如题目变成求二叉树的左视图呢? + +很简单我们只需要取 queue 的最后一个元素即可。 或者存的时候反着来也行 + +> 其实我们没必要存储 levelNodes,而是只存储每一层最右的元素,这样空间复杂度就不是 n 了, 就是 logn 了。 diff --git a/problems/2.addTwoNumbers.md b/problems/2.addTwoNumbers.md new file mode 100644 index 0000000..ecfac15 --- /dev/null +++ b/problems/2.addTwoNumbers.md @@ -0,0 +1,176 @@ +## 题目地址 +https://leetcode.com/problems/add-two-numbers/description/ + +## 题目描述 +``` +You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. + +You may assume the two numbers do not contain any leading zero, except the number 0 itself. + +Example + +Input: (2 -> 4 -> 3) + (5 -> 6 -> 4) +Output: 7 -> 0 -> 8 +Explanation: 342 + 465 = 807. + +``` +## 思路 + +设立一个表示进位的变量carried,建立一个新链表, +把输入的两个链表从头往后同时处理,每两个相加,将结果加上carried后的值作为一个新节点到新链表后面。 + +![2.addTwoNumbers](../assets/2.addTwoNumbers.gif) + +(图片来自: https://github.com/MisterBooo/LeetCodeAnimation) + +## 关键点解析 + +1. 链表这种数据结构的特点和使用 + +2. 用一个carried变量来实现进位的功能,每次相加之后计算carried,并用于下一位的计算 + +## 代码 +* 语言支持:JS,C++ + +JavaScript: +```js +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} l1 + * @param {ListNode} l2 + * @return {ListNode} + */ +var addTwoNumbers = function(l1, l2) { + if (l1 === null || l2 === null) return null + + // 使用dummyHead可以简化对链表的处理,dummyHead.next指向新链表 + let dummyHead = new ListNode(0) + let cur1 = l1 + let cur2 = l2 + let cur = dummyHead // cur用于计算新链表 + let carry = 0 // 进位标志 + + while (cur1 !== null || cur2 !== null) { + let val1 = cur1 !== null ? cur1.val : 0 + let val2 = cur2 !== null ? cur2.val : 0 + let sum = val1 + val2 + carry + let newNode = new ListNode(sum % 10) // sum%10取模结果范围为0~9,即为当前节点的值 + carry = sum >= 10 ? 1 : 0 // sum>=10,carry=1,表示有进位 + cur.next = newNode + cur = cur.next + + if (cur1 !== null) { + cur1 = cur1.next + } + + if (cur2 !== null) { + cur2 = cur2.next + } + } + + if (carry > 0) { + // 如果最后还有进位,新加一个节点 + cur.next = new ListNode(carry) + } + + return dummyHead.next +}; +``` +C++ +> C++代码与上面的JavaScript代码略有不同:将carry是否为0的判断放到了while循环中 +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +class Solution { +public: + ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { + ListNode* ret = nullptr; + ListNode* cur = nullptr; + int carry = 0; + while (l1 != nullptr || l2 != nullptr || carry != 0) { + carry += (l1 == nullptr ? 0 : l1->val) + (l2 == nullptr ? 0 : l2->val); + auto temp = new ListNode(carry % 10); + carry /= 10; + if (ret == nullptr) { + ret = temp; + cur = ret; + } + else { + cur->next = temp; + cur = cur->next; + } + l1 = l1 == nullptr ? nullptr : l1->next; + l2 = l2 == nullptr ? nullptr : l2->next; + } + return ret; + } +}; +``` +## 拓展 + +通过单链表的定义可以得知,单链表也是递归结构,因此,也可以使用递归的方式来进行reverse操作。 +> 由于单链表是线性的,使用递归方式将导致栈的使用也是线性的,当链表长度达到一定程度时,递归会导致爆栈,因此,现实中并不推荐使用递归方式来操作链表。 + +### 描述 + +1. 将两个链表的第一个节点值相加,结果转为0-10之间的个位数,并设置进位信息 +2. 将两个链表第一个节点以后的链表做带进位的递归相加 +3. 将第一步得到的头节点的next指向第二步返回的链表 + +### C++实现 +```C++ +// 普通递归 +class Solution { +public: + ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { + return addTwoNumbers(l1, l2, 0); + } + +private: + ListNode* addTwoNumbers(ListNode* l1, ListNode* l2, int carry) { + if (l1 == nullptr && l2 == nullptr && carry == 0) return nullptr; + carry += (l1 == nullptr ? 0 : l1->val) + (l2 == nullptr ? 0 : l2->val); + auto ret = new ListNode(carry % 10); + ret->next = addTwoNumbers(l1 == nullptr ? l1 : l1->next, + l2 == nullptr ? l2 : l2->next, + carry / 10); + return ret; + } +}; +// (类似)尾递归 +class Solution { +public: + ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { + ListNode* head = nullptr; + addTwoNumbers(head, nullptr, l1, l2, 0); + return head; + } + +private: + void addTwoNumbers(ListNode*& head, ListNode* cur, ListNode* l1, ListNode* l2, int carry) { + if (l1 == nullptr && l2 == nullptr && carry == 0) return; + carry += (l1 == nullptr ? 0 : l1->val) + (l2 == nullptr ? 0 : l2->val); + auto temp = new ListNode(carry % 10); + if (cur == nullptr) { + head = temp; + cur = head; + } else { + cur->next = temp; + cur = cur->next; + } + addTwoNumbers(head, cur, l1 == nullptr ? l1 : l1->next, l2 == nullptr ? l2 : l2->next, carry / 10); + } +}; +``` diff --git a/problems/20.validParentheses.md b/problems/20.validParentheses.md new file mode 100644 index 0000000..43edfcd --- /dev/null +++ b/problems/20.validParentheses.md @@ -0,0 +1,131 @@ +## 题目地址 +https://leetcode.com/problems/valid-parentheses/description + +## 题目描述 + +``` +Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid. + +An input string is valid if: + +Open brackets must be closed by the same type of brackets. +Open brackets must be closed in the correct order. +Note that an empty string is also considered valid. + +Example 1: + +Input: "()" +Output: true +Example 2: + +Input: "()[]{}" +Output: true +Example 3: + +Input: "(]" +Output: false +Example 4: + +Input: "([)]" +Output: false +Example 5: + +Input: "{[]}" +Output: true +``` + +## 思路 + +关于这道题的思路,邓俊辉讲的非常好,没有看过的同学可以看一下,[视频地址](http://www.xuetangx.com/courses/course-v1:TsinghuaX+30240184+sp/courseware/ad1a23c053df4501a3facd66ef6ccfa9/8d6f450e7f7a445098ae1d507fda80f6/)。 + +使用栈,遍历输入字符串 + +如果当前字符为左半边括号时,则将其压入栈中 + +如果遇到右半边括号时,分类讨论: + +1)如栈不为空且为对应的左半边括号,则取出栈顶元素,继续循环 + +2)若此时栈为空,则直接返回 false + +3)若不为对应的左半边括号,反之返回 false + +![20.validParentheses](../assets/20.validParentheses.gif) + +(图片来自: https://github.com/MisterBooo/LeetCodeAnimation) + +> 值得注意的是,如果题目要求只有一种括号,那么我们其实可以使用更简洁,更省内存的方式 - 计数器来进行求解,而 +不必要使用栈。 + +> 事实上,这类问题还可以进一步扩展,我们可以去解析类似 HTML 等标记语法, 比如

+ +## 关键点解析 + +1. 栈的基本特点和操作 +2. 如果你用的是 JS 没有现成的栈,可以用数组来模拟 +入: push 出:pop + +> 入: push 出 shift 就是队列 +## 代码 + +* 语言支持:JS,Python + +Javascript Code: +```js +/** + * @param {string} s + * @return {boolean} + */ +var isValid = function(s) { + let valid = true; + const stack = []; + const mapper = { + '{': "}", + "[": "]", + "(": ")" + } + + for(let i in s) { + const v = s[i]; + if (['(', '[', '{'].indexOf(v) > -1) { + stack.push(v); + } else { + const peak = stack.pop(); + if (v !== mapper[peak]) { + return false; + } + } + } + + if (stack.length > 0) return false; + + return valid; +}; +``` +Python Code: +``` + class Solution: + def isValid(self,s): + stack = [] + map = { + "{":"}", + "[":"]", + "(":")" + } + for x in s: + if x in map: + stack.append(map[x]) + else: + if len(stack)!=0: + top_element = stack.pop() + if x != top_element: + return False + else: + continue + else: + return False + return len(stack) == 0 +``` + +## 扩展 +如果让你检查 XML 标签是否闭合如何检查, 更进一步如果要你实现一个简单的 XML 的解析器,应该怎么实现? diff --git a/problems/200.number-of-islands.md b/problems/200.number-of-islands.md new file mode 100644 index 0000000..4615308 --- /dev/null +++ b/problems/200.number-of-islands.md @@ -0,0 +1,165 @@ +## 题目地址 + +https://leetcode.com/problems/number-of-islands/description/ + +## 题目描述 + +``` +Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. + +Example 1: + +Input: +11110 +11010 +11000 +00000 + +Output: 1 +Example 2: + +Input: +11000 +11000 +00100 +00011 + +Output: 3 + +``` + +## 思路 + +如图,我们其实就是要求红色区域的个数,换句话说就是求连续区域的个数。 + +![200.number-of-islands](../assets/problems/200.number-of-islands.jpg) + +符合直觉的做法是用DFS来解: + +- 我们需要建立一个 visited 数组用来记录某个位置是否被访问过。 +- 对于一个为 `1` 且未被访问过的位置,我们递归进入其上下左右位置上为 `1` 的数,将其 visited 变成 true。 +- 重复上述过程 +- 找完相邻区域后,我们将结果 res 自增1,然后我们在继续找下一个为 `1` 且未被访问过的位置,直至遍历完. + +但是这道题目只是让我们求连通区域的个数,因此我们其实不需要额外的空间去存储visited信息。 +注意到上面的过程,我们对于数字为0的其实不会进行操作的,也就是对我们“没用”。 因此对于已经访问的元素, +我们可以将其置为0即可。 + + +## 关键点解析 + +- 二维数组DFS解题模板 +- 将已经访问的元素置为0,省去visited的空间开销 + +## 代码 + +* 语言支持:JS, python3,Java + +Java Code: + +```java + public int numIslands(char[][] grid) { + if (grid == null || grid.length == 0 || grid[0].length == 0) return 0; + + int count = 0; + for (int row = 0; row < grid.length; row++) { + for (int col = 0; col < grid[0].length; col++) { + if (grid[row][col] == '1') { + dfs(grid, row, col); + count++; + } + } + } + return count; + } + + private void dfs(char[][] grid,int row,int col) { + if (row<0||row== grid.length||col<0||col==grid[0].length||grid[row][col]!='1') { + return; + } + grid[row][col] = '0'; + dfs(grid, row-1, col); + dfs(grid, row+1, col); + dfs(grid, row, col+1); + dfs(grid, row, col-1); + } +``` + +Javascript Code: +```js +/* + * @lc app=leetcode id=200 lang=javascript + * + * [200] Number of Islands + */ +function helper(grid, i, j, rows, cols) { + if (i < 0 || j < 0 || i > rows - 1 || j > cols - 1 || grid[i][j] === "0") + return; + + grid[i][j] = "0"; + + helper(grid, i + 1, j, rows, cols); + helper(grid, i, j + 1, rows, cols); + helper(grid, i - 1, j, rows, cols); + helper(grid, i, j - 1, rows, cols); +} +/** + * @param {character[][]} grid + * @return {number} + */ +var numIslands = function(grid) { + let res = 0; + const rows = grid.length; + if (rows === 0) return 0; + const cols = grid[0].length; + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (grid[i][j] === "1") { + helper(grid, i, j, rows, cols); + res++; + } + } + } + return res; +}; +``` + +python code: + +``` python +class Solution: + def numIslands(self, grid: List[List[str]]) -> int: + if not grid: return 0 + + count = 0 + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] == '1': + self.dfs(grid, i, j) + count += 1 + + return count + + def dfs(self, grid, i, j): + if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or grid[i][j] != '1': + return + grid[i][j] = '0' + self.dfs(grid, i + 1, j) + self.dfs(grid, i - 1, j) + self.dfs(grid, i, j + 1) + self.dfs(grid, i, j - 1) + +``` + +**复杂度分析** +- 时间复杂度:$O(m * n)$ +- 空间复杂度:$O(m * n)$ + +欢迎关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) + + +## 相关题目 + +- [695. 岛屿的最大面积](https://leetcode-cn.com/problems/max-area-of-island/solution/695-dao-yu-de-zui-da-mian-ji-dfspython3-by-fe-luci/) diff --git a/problems/201.bitwise-and-of-numbers-range.md b/problems/201.bitwise-and-of-numbers-range.md new file mode 100644 index 0000000..5d148cc --- /dev/null +++ b/problems/201.bitwise-and-of-numbers-range.md @@ -0,0 +1,126 @@ + +## 题目地址 +https://leetcode.com/problems/bitwise-and-of-numbers-range/description/ + +## 题目描述 + +``` +Given a range [m, n] where 0 <= m <= n <= 2147483647, return the bitwise AND of all numbers in this range, inclusive. + +Example 1: + +Input: [5,7] +Output: 4 +Example 2: + +Input: [0,1] +Output: 0 + +``` + +## 思路 + +一个显而易见的解法是, 从m到n依次进行`求与`的操作。 + +```js + + let res = m; + for (let i = m + 1; i <= n; i++) { + res = res & i; + } + return res; + +``` + +但是, 如果你把这个solution提交的话,很显然不会通过, 会超时。 + +我们依旧还是用trick来简化操作。 我们利用的性质是, n个连续数字求与的时候,前m位都是1. + +举题目给的例子:[5,7] 共 5, 6,7三个数字, 用二进制表示 101, 110,111, +这三个数字特点是第一位都是1,后面几位求与一定是0. + +再来一个明显的例子:[20, 24], 共 20, 21, 22, 23,24五个数字,二进制表示就是 + +``` +0001 0100 +0001 0101 +0001 0110 +0001 0111 +0001 1000 +``` + +这五个数字特点是第四位都是1,后面几位求与一定是0. + +因此我们的思路就是, 求出这个数字区间的数字前多少位都是1了,那么他们求与的结果一定是前几位数字,然后后面都是0. + + +## 关键点解析 + + +- n个连续数字求与的时候,前m位都是1 + +- 可以用递归实现, 个人认为比较难想到 + +- bit 运算 + +代码: + +```js + +(n > m) ? (rangeBitwiseAnd(m/2, n/2) << 1) : m; + +``` + +> 每次问题规模缩小一半, 这是二分法吗? + +## 代码 + +语言支持:JavaSCript,Python3 + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=201 lang=javascript + * + * [201] Bitwise AND of Numbers Range + * + */ +/** + * @param {number} m + * @param {number} n + * @return {number} + */ +var rangeBitwiseAnd = function(m, n) { + let count = 0; + while (m !== n) { + m = m >> 1; + n = n >> 1; + count++; + } + + return n << count; +}; + +``` + +Python Code: + +```python +class Solution: + def rangeBitwiseAnd(self, m: int, n: int) -> int: + cnt = 0 + while m != n: + m >>= 1 + n >>= 1 + cnt += 1 + + return m << cnt + ``` + + **复杂度分析** + + - 时间复杂度:最坏的情况我们需要循环N次,最好的情况是一次都不需要, 因此时间复杂度取决于我们移动的位数,具体移动的次数取决于我们的输入,平均来说时间复杂度为 $O(N)$,其中N为M和N的二进制表示的位数。 + - 空间复杂度:$O(1)$ + + diff --git a/problems/203.remove-linked-list-elements.md b/problems/203.remove-linked-list-elements.md new file mode 100644 index 0000000..7955c00 --- /dev/null +++ b/problems/203.remove-linked-list-elements.md @@ -0,0 +1,90 @@ +## 题目地址 +https://leetcode.com/problems/remove-linked-list-elements/description/ + +## 题目描述 +``` +Remove all elements from a linked list of integers that have value val. + +Example: + +Input: 1->2->6->3->4->5->6, val = 6 +Output: 1->2->3->4->5 + +``` + +## 思路 +这个一个链表基本操作的题目,思路就不多说了。 +## 关键点解析 + +- 链表的基本操作(删除指定节点) +- 虚拟节点 dummy 简化操作 + +> 其实设置 dummy 节点就是为了处理特殊位置(头节点),这这道题就是如果头节点是给定的需要删除的节点呢? +为了保证代码逻辑的一致性,即不需要为头节点特殊定制逻辑,才采用的虚拟节点。 + +- 如果连续两个节点都是要删除的节点,这个情况容易被忽略。 +eg: + +```js +// 只有下个节点不是要删除的节点才更新 current +if (!next || next.val !== val) { + current = next; +} + +``` + +## 代码 + +* 语言支持:JS,Python + +Javascript Code: + +```js +/** + * @param {ListNode} head + * @param {number} val + * @return {ListNode} + */ +var removeElements = function(head, val) { + const dummy = { + next: head + } + let current = dummy; + + while(current && current.next) { + let next = current.next; + if (next.val === val) { + current.next = next.next; + next = next.next; + } + + if (!next || next.val !== val) { + current = next; + } + } + + return dummy.next; +}; +``` + +Python Code: + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def removeElements(self, head: ListNode, val: int) -> ListNode: + prev = ListNode(0) + prev.next = head + cur = prev + while cur.next: + if cur.next.val == val: + cur.next = cur.next.next + else: + cur = cur.next + return prev.next +``` diff --git a/problems/206.reverse-linked-list.md b/problems/206.reverse-linked-list.md new file mode 100644 index 0000000..2a29e71 --- /dev/null +++ b/problems/206.reverse-linked-list.md @@ -0,0 +1,225 @@ +## 题目地址 +https://leetcode.com/problems/reverse-linked-list/description/ + +## 题目描述 +Reverse a singly linked list. + +Example: + +Input: 1->2->3->4->5->NULL +Output: 5->4->3->2->1->NULL +Follow up: + +A linked list can be reversed either iteratively or recursively. Could you implement both? + +## 思路 +这个就是常规操作了,使用一个变量记录前驱 pre,一个变量记录后继 next. + +不断更新`current.next = pre` 就好了 +## 关键点解析 + +- 链表的基本操作(交换) +- 虚拟节点 dummy 简化操作 +- 注意更新 current 和 pre 的位置, 否则有可能出现溢出 + +## 代码 + +语言支持:JS, C++, Python,Java + +JavaScript Code: + +```js +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ +var reverseList = function(head) { + if (!head || !head.next) return head; + + let cur = head; + let pre = null; + + while(cur) { + const next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + + return pre; +}; + +``` + +C++ Code: + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +class Solution { +public: + ListNode* reverseList(ListNode* head) { + ListNode* prev = NULL; + ListNode* cur = head; + ListNode* next = NULL; + while (cur != NULL) { + next = cur->next; + cur->next = prev; + prev = cur; + cur = next; + } + return prev; + } +}; +``` + +Python Code: + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + if not head: return None + prev = None + cur = head + while cur: + cur.next, prev, cur = prev, cur, cur.next + return prev +``` + +Java Code: + +```java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { val = x; } + * } + */ +class Solution { + public ListNode reverseList(ListNode head) { + ListNode pre = null, cur = head; + + while (cur != null) { + ListNode next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + + return pre; + } +} +``` + +## 拓展 + +通过单链表的定义可以得知,单链表也是递归结构,因此,也可以使用递归的方式来进行 reverse 操作。 +> 由于单链表是线性的,使用递归方式将导致栈的使用也是线性的,当链表长度达到一定程度时,递归会导致爆栈,因此,现实中并不推荐使用递归方式来操作链表。 + +### 描述 + +1. 除第一个节点外,递归将链表 reverse +2. 将第一个节点添加到已 reverse 的链表之后 + +> 这里需要注意的是,每次需要保存已经 reverse 的链表的头节点和尾节点 + +### C++实现 +```c++ +// 普通递归 +class Solution { +public: + ListNode* reverseList(ListNode* head) { + ListNode* tail = nullptr; + return reverseRecursive(head, tail); + } + + ListNode* reverseRecursive(ListNode *head, ListNode *&tail) { + if (head == nullptr) { + tail = nullptr; + return head; + } + if (head->next == nullptr) { + tail = head; + return head; + } + auto h = reverseRecursive(head->next, tail); + if (tail != nullptr) { + tail->next = head; + tail = head; + head->next = nullptr; + } + return h; + } +}; + +// (类似)尾递归 +class Solution { +public: + ListNode* reverseList(ListNode* head) { + if (head == nullptr) return head; + return reverseRecursive(nullptr, head, head->next); + } + + ListNode* reverseRecursive(ListNode *prev, ListNode *head, ListNode *next) + { + if (next == nullptr) return head; + auto n = next->next; + next->next = head; + head->next = prev; + return reverseRecursive(head, next, n); + } +}; +``` + +### JavaScript 实现 +```javascript +var reverseList = function(head) { + // 递归结束条件 + if (head === null || head.next === null) { + return head + } + + // 递归反转 子链表 + let newReverseList = reverseList(head.next) + // 获取原来链表的第 2 个节点 newReverseListTail + let newReverseListTail = head.next + // 调整原来头结点和第 2 个节点的指向 + newReverseListTail.next = head + head.next = null + + // 将调整后的链表返回 + return newReverseList +} +``` + +### Python 实现 +```python +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + if not head or not head.next: return head + ans = self.reverseList(head.next) + head.next.next = head + head.next = None + return ans +``` diff --git a/problems/208.implement-trie-prefix-tree.md b/problems/208.implement-trie-prefix-tree.md new file mode 100644 index 0000000..959d3e9 --- /dev/null +++ b/problems/208.implement-trie-prefix-tree.md @@ -0,0 +1,196 @@ +## 题目地址 + +https://leetcode.com/problems/implement-trie-prefix-tree/description/ + +## 题目描述 + +``` +Implement a trie with insert, search, and startsWith methods. + +Example: + +Trie trie = new Trie(); + +trie.insert("apple"); +trie.search("apple"); // returns true +trie.search("app"); // returns false +trie.startsWith("app"); // returns true +trie.insert("app"); +trie.search("app"); // returns true +Note: + +You may assume that all inputs are consist of lowercase letters a-z. +All inputs are guaranteed to be non-empty strings. + +``` + +## 思路 + +这是一道很直接的题目,上来就让你实现`前缀树(字典树)`。这算是基础数据结构中的 +知识了,不清楚什么是字典树的可以查阅相关资料。 + +我们看到题目给出的使用方法`new Trie`, `insert`,`search`和`startWith`. + +为了区分`search`和`startWith`我们需要增加一个标示来区分当前节点是否是某个单词的结尾。 +因此节点的数据结构应该是: + +```js +function TrieNode(val) { + this.val = val; // 当前的字母 + this.children = []; // 题目要求字典仅有a-z,那么其长度最大为26(26个字母) + this.isWord = false; +} +``` + +每次 insert 我们其实都是从根节点出发,一个一个找到我们需要添加的节点,修改 children 的值. + +我们应该修改哪一个 child 呢? 我们需要一个函数来计算索引 + +```js +function computeIndex(c) { + return c.charCodeAt(0) - "a".charCodeAt(0); +} +``` + +其实不管 insert, search 和 startWith 的逻辑都是差不多的,都是从 root 出发, +找到我们需要操作的 child, 然后进行相应操作(添加,修改,返回)。 + +![208.implement-trie-prefix-tree-1](../assets/problems/208.implement-trie-prefix-tree-1.png) + +## 关键点解析 + +- 前缀树 + +- 核心逻辑 + +```js + const c = word[i]; + const current = computeIndex(c) +if (!ws.children[current]) { + ws.children[current] = new TrieNode(c); + } + ws = ws.children[current]; // 深度递增 +} + +``` + +## 代码 + +```js +/* + * @lc app=leetcode id=208 lang=javascript + * + * [208] Implement Trie (Prefix Tree) + * + * https://leetcode.com/problems/implement-trie-prefix-tree/description/ + * + * algorithms + * Medium (36.93%) + * Total Accepted: 172K + * Total Submissions: 455.5K + * Testcase Example: '["Trie","insert","search","search","startsWith","insert","search"]\n[[],["apple"],["apple"],["app"],["app"],["app"],["app"]]' + * + * Implement a trie with insert, search, and startsWith methods. + * + * Example: + * + * + * Trie trie = new Trie(); + * + * trie.insert("apple"); + * trie.search("apple"); // returns true + * trie.search("app"); // returns false + * trie.startsWith("app"); // returns true + * trie.insert("app"); + * trie.search("app"); // returns true + * + * + * Note: + * + * + * You may assume that all inputs are consist of lowercase letters a-z. + * All inputs are guaranteed to be non-empty strings. + * + * + */ +function TrieNode(val) { + this.val = val; + this.children = []; + this.isWord = false; +} + +function computeIndex(c) { + return c.charCodeAt(0) - "a".charCodeAt(0); +} +/** + * Initialize your data structure here. + */ +var Trie = function() { + this.root = new TrieNode(null); +}; + +/** + * Inserts a word into the trie. + * @param {string} word + * @return {void} + */ +Trie.prototype.insert = function(word) { + let ws = this.root; + for (let i = 0; i < word.length; i++) { + const c = word[i]; + const current = computeIndex(c); + if (!ws.children[current]) { + ws.children[current] = new TrieNode(c); + } + ws = ws.children[current]; + } + ws.isWord = true; +}; + +/** + * Returns if the word is in the trie. + * @param {string} word + * @return {boolean} + */ +Trie.prototype.search = function(word) { + let ws = this.root; + for (let i = 0; i < word.length; i++) { + const c = word[i]; + const current = computeIndex(c); + if (!ws.children[current]) return false; + ws = ws.children[current]; + } + return ws.isWord; +}; + +/** + * Returns if there is any word in the trie that starts with the given prefix. + * @param {string} prefix + * @return {boolean} + */ +Trie.prototype.startsWith = function(prefix) { + let ws = this.root; + for (let i = 0; i < prefix.length; i++) { + const c = prefix[i]; + const current = computeIndex(c); + if (!ws.children[current]) return false; + ws = ws.children[current]; + } + return true; +}; + +/** + * Your Trie object will be instantiated and called as such: + * var obj = new Trie() + * obj.insert(word) + * var param_2 = obj.search(word) + * var param_3 = obj.startsWith(prefix) + */ +``` + +## 相关题目 + +- [0211.add-and-search-word-data-structure-design](./211.add-and-search-word-data-structure-design.md) +- [0212.word-search-ii](./212.word-search-ii.md) +- [0472.concatenated-words](./problems/472.concatenated-words.md) +- [0820.short-encoding-of-words](https://github.com/azl397985856/leetcode/blob/master/problems/820.short-encoding-of-words.md) diff --git a/problems/209.minimum-size-subarray-sum.md b/problems/209.minimum-size-subarray-sum.md new file mode 100644 index 0000000..df3c648 --- /dev/null +++ b/problems/209.minimum-size-subarray-sum.md @@ -0,0 +1,157 @@ +## 题目地址 + +https://leetcode.com/problems/minimum-size-subarray-sum/description/ + +## 题目描述 + +``` +Given an array of n positive integers and a positive integer s, find the minimal length of a contiguous subarray of which the sum ≥ s. If there isn't one, return 0 instead. + +Example: + +Input: s = 7, nums = [2,3,1,2,4,3] +Output: 2 +Explanation: the subarray [4,3] has the minimal length under the problem constraint. +Follow up: +If you have figured out the O(n) solution, try coding another solution of which the time complexity is O(n log n). + +``` + +## 思路 + +用滑动窗口来记录序列, 每当滑动窗口中的 sum 超过 s, 就去更新最小值,并根据先进先出的原则更新滑动窗口,直至 sum 刚好小于 s + +![209.minimum-size-subarray-sum](../assets/problems/209.minimum-size-subarray-sum.png) + +> 这道题目和 leetcode 3 号题目有点像,都可以用滑动窗口的思路来解决 + +## 关键点 + +- 滑动窗口简化操作(滑窗口适合用于求解这种要求`连续`的题目) + +## 代码 + +- 语言支持:JS,C++,Python + +Python Code: + +```python + +class Solution: + def minSubArrayLen(self, s: int, nums: List[int]) -> int: + l = total = 0 + ans = len(nums) + 1 + for r in range(len(nums)): + total += nums[r] + while total >= s: + ans = min(ans, r - l + 1) + total -= nums[l] + l += 1 + return 0 if ans == len(nums) + 1 else ans +``` + + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=209 lang=javascript + * + * [209] Minimum Size Subarray Sum + * + */ +/** + * @param {number} s + * @param {number[]} nums + * @return {number} + */ +var minSubArrayLen = function(s, nums) { + if (nums.length === 0) return 0; + const slideWindow = []; + let acc = 0; + let min = null; + + for (let i = 0; i < nums.length + 1; i++) { + const num = nums[i]; + + while (acc >= s) { + if (min === null || slideWindow.length < min) { + min = slideWindow.length; + } + acc = acc - slideWindow.shift(); + } + + slideWindow.push(num); + + acc = slideWindow.reduce((a, b) => a + b, 0); + } + + return min || 0; +}; +``` + +C++ Code: + +```C++ +class Solution { +public: + int minSubArrayLen(int s, vector& nums) { + int num_len= nums.size(); + int left=0, right=0, total=0, min_len= num_len+1; + while (right < num_len) { + do { + total += nums[right++]; + } while (right < num_len && total < s); + while (left < right && total - nums[left] >= s) total -= nums[left++]; + if (total >=s && min_len > right - left) + min_len = right- left; + } + return min_len <= num_len ? min_len: 0; + } +}; +``` + +**复杂度分析** +- 时间复杂度:$O(N)$,其中 N 为数组大小。 +- 空间复杂度:$O(1)$ + +欢迎关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) + +## 扩展 + +如果题目要求是 sum = s, 而不是 sum >= s 呢? + +eg: + +```js +var minSubArrayLen = function(s, nums) { + if (nums.length === 0) return 0; + const slideWindow = []; + let acc = 0; + let min = null; + + for (let i = 0; i < nums.length + 1; i++) { + const num = nums[i]; + + while (acc > s) { + acc = acc - slideWindow.shift(); + } + if (acc === s) { + if (min === null || slideWindow.length < min) { + min = slideWindow.length; + } + slideWindow.shift(); + } + + slideWindow.push(num); + + acc = slideWindow.reduce((a, b) => a + b, 0); + } + + return min || 0; +}; +``` + + diff --git a/problems/21.MergeTwoSortedLists.md b/problems/21.MergeTwoSortedLists.md new file mode 100644 index 0000000..86b5ce7 --- /dev/null +++ b/problems/21.MergeTwoSortedLists.md @@ -0,0 +1,63 @@ +## 题目地址 +https://leetcode-cn.com/problems/merge-two-sorted-lists + +## 题目描述 +``` +将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。  + +示例: + +输入:1->2->4, 1->3->4 +输出:1->1->2->3->4->4 + +``` + +## 思路 +使用递归来解题,将两个链表头部较小的一个与剩下的元素合并,并返回排好序的链表头,当两条链表中的一条为空时终止递归。 + + +## 关键点 + +- 掌握链表数据结构 +- 考虑边界情况 + +## 代码 +* 语言支持:JS + +```js +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} l1 + * @param {ListNode} l2 + * @return {ListNode} + */ +const mergeTwoLists = function (l1, l2) { + if (l1 === null) { + return l2; + } + if (l2 === null) { + return l1; + } + if (l1.val < l2.val) { + l1.next = mergeTwoLists(l1.next, l2); + return l1; + } else { + l2.next = mergeTwoLists(l1, l2.next); + return l2; + } +}; +``` + +***复杂度分析*** + +M、N 是两条链表 l1、l2 的长度 + +- 时间复杂度:O(M+N) +- 空间复杂度:O(M+N) + diff --git a/problems/211.add-and-search-word-data-structure-design.md b/problems/211.add-and-search-word-data-structure-design.md new file mode 100644 index 0000000..a0b4037 --- /dev/null +++ b/problems/211.add-and-search-word-data-structure-design.md @@ -0,0 +1,172 @@ +## 题目地址(211. 添加与搜索单词 - 数据结构设计) + +https://leetcode-cn.com/problems/add-and-search-word-data-structure-design/description/ + +## 题目描述 + +``` +设计一个支持以下两种操作的数据结构: + +void addWord(word) +bool search(word) +search(word) 可以搜索文字或正则表达式字符串,字符串只包含字母 . 或 a-z 。 . 可以表示任何一个字母。 + +示例: + +addWord("bad") +addWord("dad") +addWord("mad") +search("pad") -> false +search("bad") -> true +search(".ad") -> true +search("b..") -> true +说明: + +你可以假设所有单词都是由小写字母 a-z 组成的。 + +``` + +## 思路 + +我们首先不考虑字符"."的情况。这种情况比较简单,我们 addWord 直接添加到数组尾部,search 则线性查找即可。 + +接下来我们考虑特殊字符“.”,其实也不难,只不过 search 的时候,判断如果是“.”, 我们认为匹配到了,继续往后匹配即可。 + +上面的代码复杂度会比较高,我们考虑优化。如果你熟悉前缀树的话,应该注意到这可以使用前缀树来进行优化。前缀树优化之后每次查找复杂度是$O(h)$, 其中 h 是前缀树深度,也就是最长的字符串长度。 + +关于前缀树,LeetCode 有很多题目。有的是直接考察,让你实现一个前缀树,有的是间接考察,比如本题。前缀树代码见下方,大家之后可以直接当成前缀树的解题模板使用。 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gb5dmstsxxj30mz0gqmzh.jpg) + +由于我们这道题需要考虑特殊字符".",因此我们需要对标准前缀树做一点改造,insert 不做改变,我们只需要改变 search 即可,代码(Python 3): + +```python +def search(self, word): + """ + Returns if the word is in the trie. + :type word: str + :rtype: bool + """ + curr = self.Trie + for i, w in enumerate(word): + if w == '.': + wizards = [] + for k in curr.keys(): + if k == '#': + continue + wizards.append(self.search(word[:i] + k + word[i + 1:])) + return any(wizards) + if w not in curr: + return False + curr = curr[w] + return "#" in curr +``` + +标准的前缀树搜索我也贴一下代码,大家可以对比一下: + +```python +def search(self, word): + """ + Returns if the word is in the trie. + :type word: str + :rtype: bool + """ + curr = self.Trie + for w in word: + if w not in curr: + return False + curr = curr[w] + return "#" in curr +``` + +## 关键点 + +- 前缀树(也叫字典树),英文名 Trie(读作 tree 或者 try) + +## 代码 + +- 语言支持:Python3 + +Python3 Code: + +关于 Trie 的代码: + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.Trie = {} + + def insert(self, word): + """ + Inserts a word into the trie. + :type word: str + :rtype: void + """ + curr = self.Trie + for w in word: + if w not in curr: + curr[w] = {} + curr = curr[w] + curr['#'] = 1 + + def search(self, word): + """ + Returns if the word is in the trie. + :type word: str + :rtype: bool + """ + curr = self.Trie + for i, w in enumerate(word): + if w == '.': + wizards = [] + for k in curr.keys(): + if k == '#': + continue + wizards.append(self.search(word[:i] + k + word[i + 1:])) + return any(wizards) + if w not in curr: + return False + curr = curr[w] + return "#" in curr +``` + +主逻辑代码: + +```python +class WordDictionary: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.trie = Trie() + + def addWord(self, word: str) -> None: + """ + Adds a word into the data structure. + """ + self.trie.insert(word) + + def search(self, word: str) -> bool: + """ + Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. + """ + return self.trie.search(word) + + +# Your WordDictionary object will be instantiated and called as such: +# obj = WordDictionary() +# obj.addWord(word) +# param_2 = obj.search(word) +``` + +## 相关题目 + +- [0208.implement-trie-prefix-tree](./208.implement-trie-prefix-tree.md) +- [0212.word-search-ii](./212.word-search-ii.md) +- [0472.concatenated-words](./problems/472.concatenated-words.md) +- [0820.short-encoding-of-words](https://github.com/azl397985856/leetcode/blob/master/problems/820.short-encoding-of-words.md) diff --git a/problems/212.word-search-ii.md b/problems/212.word-search-ii.md new file mode 100644 index 0000000..fe978fa --- /dev/null +++ b/problems/212.word-search-ii.md @@ -0,0 +1,142 @@ +## 题目地址(212. 单词搜索 II) + +https://leetcode-cn.com/problems/word-search-ii/description/ + +## 题目描述 + +``` +给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。 + +单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。 + +示例: + +输入: +words = ["oath","pea","eat","rain"] and board = +[ + ['o','a','a','n'], + ['e','t','a','e'], + ['i','h','k','r'], + ['i','f','l','v'] +] + +输出: ["eat","oath"] +说明: +你可以假设所有输入都由小写字母 a-z 组成。 + +提示: + +你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯? +如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看这个问题: 实现Trie(前缀树)。 + +``` + +## 思路 + +我们需要对矩阵中每一项都进行深度优先遍历(DFS)。 递归的终点是 + +1. 超出边界 +2. 递归路径上组成的单词不在 words 的前缀。 + +比如题目示例:words = ["oath","pea","eat","rain"],那么对于 oa,oat 满足条件,因为他们都是 oath 的前缀,但是 oaa 就不满足条件。 + +为了防止环的出现,我们需要记录访问过的节点。而返回结果是需要去重的。出于简单考虑,我们使用集合(set),最后返回的时候重新转化为 list。 + +刚才我提到了一个关键词“前缀”,我们考虑使用前缀树来优化。使得复杂度降低为$O(h)$, 其中 h 是前缀树深度,也就是最长的字符串长度。 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gb5dmstsxxj30mz0gqmzh.jpg) + +## 关键点 + +- 前缀树(也叫字典树),英文名 Trie(读作 tree 或者 try) +- DFS +- hashmap 结合 dfs 记录访问过的元素的时候,注意结束之后需要将 hashmap 的值重置。(下方代码的`del seen[(i, j)]`) + +## 代码 + +- 语言支持:Python3 + +Python3 Code: + +关于 Trie 的代码: + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.Trie = {} + + def insert(self, word): + """ + Inserts a word into the trie. + :type word: str + :rtype: void + """ + curr = self.Trie + for w in word: + if w not in curr: + curr[w] = {} + curr = curr[w] + curr['#'] = 1 + + def startsWith(self, prefix): + """ + Returns if there is any word in the trie that starts with the given prefix. + :type prefix: str + :rtype: bool + """ + + curr = self.Trie + for w in prefix: + if w not in curr: + return False + curr = curr[w] + return True +``` + +主逻辑代码: + +```python +class Solution: + def findWords(self, board: List[List[str]], words: List[str]) -> List[str]: + m = len(board) + if m == 0: + return [] + n = len(board[0]) + trie = Trie() + seen = None + res = set() + for word in words: + trie.insert(word) + + def dfs(s, i, j): + if (i, j) in seen or i < 0 or i >= m or j < 0 or j >= n or not trie.startsWith(s): + return + s += board[i][j] + seen[(i, j)] = True + + if s in words: + res.add(s) + dfs(s, i + 1, j) + dfs(s, i - 1, j) + dfs(s, i, j + 1) + dfs(s, i, j - 1) + + del seen[(i, j)] + + for i in range(m): + for j in range(n): + seen = dict() + dfs("", i, j) + return list(res) +``` + +## 相关题目 + +- [0208.implement-trie-prefix-tree](./208.implement-trie-prefix-tree.md) +- [0211.add-and-search-word-data-structure-design](./211.add-and-search-word-data-structure-design.md) +- [0472.concatenated-words](./problems/472.concatenated-words.md) +- [0820.short-encoding-of-words](https://github.com/azl397985856/leetcode/blob/master/problems/820.short-encoding-of-words.md) diff --git a/problems/215.kth-largest-element-in-an-array.md b/problems/215.kth-largest-element-in-an-array.md new file mode 100644 index 0000000..47f7415 --- /dev/null +++ b/problems/215.kth-largest-element-in-an-array.md @@ -0,0 +1,155 @@ +## 题目地址 +https://leetcode.com/problems/kth-largest-element-in-an-array/ + +## 题目描述 + +``` +Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element. + +Example 1: + +Input: [3,2,1,5,6,4] and k = 2 +Output: 5 +Example 2: + +Input: [3,2,3,1,2,4,5,5,6] and k = 4 +Output: 4 +Note: +You may assume k is always valid, 1 ≤ k ≤ array's length. +``` + +## 思路 + +这道题要求在一个无序的数组中,返回第K大的数。根据时间复杂度不同,这题有3种不同的解法。 + +#### 解法一 (排序) +很直观的解法就是给数组排序,这样求解第`K`大的数,就等于是从小到大排好序的数组的第`(n-K)`小的数 (n 是数组的长度)。 + +例如: +``` +[3,2,1,5,6,4], k = 2 +1. 数组排序: + [1,2,3,4,5,6], +2. 找第(n-k)小的数 + n-k=4, nums[4]=5 (第2大的数) +``` +*时间复杂度:* `O(nlogn) - n 是数组长度。` + +#### 解法二 - 小顶堆(Heap) +可以维护一个大小为`K`的小顶堆,堆顶是最小元素,当堆的`size > K` 的时候,删除堆顶元素. +扫描一遍数组,最后堆顶就是第`K`大的元素。 直接返回。 + +例如: +![heap](../assets/problems/215.kth-largest-element-in-an-array-heap.jpg) + +*时间复杂度*:`O(n * logk) , n is array length` +*空间复杂度*:`O(k)` + +跟排序相比,以空间换时间。 + +#### 解法三 - Quick Select + +Quick Select 类似快排,选取pivot,把小于pivot的元素都移到pivot之前,这样pivot所在位置就是第pivot index 小的元素。 +但是不需要完全给数组排序,只要找到当前pivot的位置是否是在第(n-k)小的位置,如果是,找到第k大的数直接返回。 + +具体步骤: +``` +1. 在数组区间随机取`pivot index = left + random[right-left]`. +2. 根据pivot 做 partition,在数组区间,把小于pivot的数都移到pivot左边。 +3. 得到pivot的位置 index,`compare(index, (n-k))`. + a. index == n-k -> 找到第`k`大元素,直接返回结果。 + b. index < n-k -> 说明在`index`右边,继续找数组区间`[index+1, right]` + c. index > n-k -> 那么第`k`大数在`index`左边,继续查找数组区间`[left, index-1]`. + +例子,【3,2,3,1,2,4,5,5,6], k = 4 + +如下图: +``` +![quick select](../assets/problems/215.kth-largest-element-in-an-array-quick-select.jpg) + +*时间复杂度*: + - 平均是:`O(n)` + - 最坏的情况是:`O(n * n)` + +## 关键点分析 +1. 直接排序很简单 +2. 堆(Heap)主要是要维护一个K大小的小顶堆,扫描一遍数组,最后堆顶元素即是所求。 +3. Quick Select, 关键是是取pivot,对数组区间做partition,比较pivot的位置,类似二分,取pivot左边或右边继续递归查找。 + +## 代码(Java code) +*解法一 - 排序* +```java +class KthLargestElementSort { + public int findKthlargest2(int[] nums, int k) { + Arrays.sort(nums); + return nums[nums.length - k]; + } +} +``` + +*解法二 - Heap (PriorityQueue)* +```java +class KthLargestElementHeap { + public int findKthLargest(int[] nums, int k) { + PriorityQueue pq = new PriorityQueue<>(); + for (int num : nums) { + pq.offer(num); + if (pq.size() > k) { + pq.poll(); + } + } + return pq.poll(); + } +} +``` + +*解法三 - Quick Select* +```java +class KthLargestElementQuickSelect { + static Random random = new Random(); + public int findKthLargest3(int[] nums, int k) { + int len = nums.length; + return select(nums, 0, len - 1, len - k); + } + + private int select(int[] nums, int left, int right, int k) { + if (left == right) return nums[left]; + // random select pivotIndex between left and right + int pivotIndex = left + random.nextInt(right - left); + // do partition, move smaller than pivot number into pivot left + int pos = partition(nums, left, right, pivotIndex); + if (pos == k) { + return nums[pos]; + } else if (pos > k) { + return select(nums, left, pos - 1, k); + } else { + return select(nums, pos + 1, right, k); + } + } + + private int partition(int[] nums, int left, int right, int pivotIndex) { + int pivot = nums[pivotIndex]; + // move pivot to end + swap(nums, right, pivotIndex); + int pos = left; + // move smaller num to pivot left + for (int i = left; i <= right; i++) { + if (nums[i] < pivot) { + swap(nums, pos++, i); + } + } + // move pivot to original place + swap(nums, right, pos); + return pos; + } + + private void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } +} +``` + +## 参考(References) +1. [Quick Select Wiki](https://en.wikipedia.org/wiki/Quickselect) \ No newline at end of file diff --git a/problems/219.contains-duplicate-ii.md b/problems/219.contains-duplicate-ii.md new file mode 100644 index 0000000..c2c85b9 --- /dev/null +++ b/problems/219.contains-duplicate-ii.md @@ -0,0 +1,137 @@ + +## 题目地址 +https://leetcode.com/problems/contains-duplicate-ii/description/ + +## 题目描述 + + +``` +Given an array of integers and an integer k, find out whether there are two distinct indices i and j in the array such that nums[i] = nums[j] and the absolute difference between i and j is at most k. + +Example 1: + +Input: nums = [1,2,3,1], k = 3 +Output: true +Example 2: + +Input: nums = [1,0,1,1], k = 1 +Output: true +Example 3: + +Input: nums = [1,2,3,1,2,3], k = 2 +Output: false + +``` + +## 思路 + +由于题目没有对空间复杂度有求,用一个hashmap 存储已经访问过的数字即可, +每次访问都会看hashmap中是否有这个元素,有的话拿出索引进行比对,是否满足条件(相隔不大于k),如果满足返回true即可。 + + +## 关键点解析 + +无 + + +## 代码 + +* 语言支持:JS,Python,C++ + +Javascript Code: + +```js +/* + * @lc app=leetcode id=219 lang=javascript + * + * [219] Contains Duplicate II + * + * https://leetcode.com/problems/contains-duplicate-ii/description/ + * + * algorithms + * Easy (34.75%) + * Total Accepted: 187.3K + * Total Submissions: 537.5K + * Testcase Example: '[1,2,3,1]\n3' + * + * Given an array of integers and an integer k, find out whether there are two + * distinct indices i and j in the array such that nums[i] = nums[j] and the + * absolute difference between i and j is at most k. + * + * + * Example 1: + * + * + * Input: nums = [1,2,3,1], k = 3 + * Output: true + * + * + * + * Example 2: + * + * + * Input: nums = [1,0,1,1], k = 1 + * Output: true + * + * + * + * Example 3: + * + * + * Input: nums = [1,2,3,1,2,3], k = 2 + * Output: false + * + * + * + * + * + */ +/** + * @param {number[]} nums + * @param {number} k + * @return {boolean} + */ +var containsNearbyDuplicate = function(nums, k) { + const visited = {}; + for(let i = 0; i < nums.length; i++) { + const num = nums[i]; + if (visited[num] !== undefined && i - visited[num] <= k) { + return true; + } + visited[num] = i; + } + return false +}; +``` + +Python Code: + +```python +class Solution: + def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool: + d = {} + for index, num in enumerate(nums): + if num in d and index - d[num] <= k: + return True + d[num] = index + return False +``` +C++ Code: +```C++ +class Solution { +public: + bool containsNearbyDuplicate(vector& nums, int k) { + auto m = unordered_map(); + for (int i = 0; i < nums.size(); ++i) { + auto iter = m.find(nums[i]); + if (iter != m.end()) { + if (i - m[nums[i]] <= k) { + return true; + } + } + m[nums[i]] = i; + } + return false; + } +}; +``` diff --git a/problems/22.GenerateParentheses.md b/problems/22.GenerateParentheses.md new file mode 100644 index 0000000..352f265 --- /dev/null +++ b/problems/22.GenerateParentheses.md @@ -0,0 +1,72 @@ +## 题目地址 +https://leetcode-cn.com/problems/generate-parentheses + +## 题目描述 +``` +数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 + +示例: + +输入:n = 3 +输出:[ + "((()))", + "(()())", + "(())()", + "()(())", + "()()()" + ] + +``` + +## 思路 + +深度优先搜索(回溯思想),从空字符串开始构造,做加法。 + + +## 关键点 + +- 当 l < r 时记得剪枝 + + +## 代码 +* 语言支持:JS + +```js +/** + * @param {number} n + * @return {string[]} + * @param l 左括号已经用了几个 + * @param r 右括号已经用了几个 + * @param str 当前递归得到的拼接字符串结果 + * @param res 结果集 + */ +const generateParenthesis = function (n) { + const res = []; + + function dfs(l, r, str) { + if (l == n && r == n) { + return res.push(str); + } + // l 小于 r 时不满足条件 剪枝 + if (l < r) { + return; + } + // l 小于 n 时可以插入左括号,最多可以插入 n 个 + if (l < n) { + dfs(l + 1, r, str + '('); + } + // r < l 时 可以插入右括号 + if (r < l) { + dfs(l, r + 1, str + ')'); + } + } + dfs(0, 0, ''); + return res; +}; +``` + + +***复杂度分析*** + +- 时间复杂度:O(2^N) +- 空间复杂度:O(2^N) diff --git a/problems/221.maximal-square.md b/problems/221.maximal-square.md new file mode 100644 index 0000000..7053d7b --- /dev/null +++ b/problems/221.maximal-square.md @@ -0,0 +1,127 @@ + +## 题目地址 + +https://leetcode.com/problems/maximal-square/ + +## 题目描述 + + +``` +Given a 2D binary matrix filled with 0's and 1's, find the largest square containing only 1's and return its area. + +Example: + +Input: + +1 0 1 0 0 +1 0 1 1 1 +1 1 1 1 1 +1 0 0 1 0 + +Output: 4 +``` + +## 思路 + +![221.maximal-square](../assets/problems/221.maximal-square-1.jpg) + +符合直觉的做法是暴力求解处所有的正方形,逐一计算面积,然后记录最大的。这种时间复杂度很高。 + +我们考虑使用动态规划,我们使用dp[i][j]表示以matrix[i][j]为右下角的顶点的可以组成的最大正方形的边长。 +那么我们只需要计算所有的i,j组合,然后求出最大值即可。 + +我们来看下dp[i][j] 怎么推导。 首先我们要看matrix[i][j], 如果matrix[i][j]等于0,那么就不用看了,直接等于0。 +如果matrix[i][j]等于1,那么我们将matrix[[i][j]分别往上和往左进行延伸,直到碰到一个0为止。 + +如图 dp[3][3] 的计算。 matrix[3][3]等于1,我们分别往上和往左进行延伸,直到碰到一个0为止,上面长度为1,左边为3。 +dp[2][2]等于1(之前已经计算好了),那么其实这里的瓶颈在于三者的最小值, 即`Min(1, 1, 3)`, 也就是`1`。 那么dp[3][3] 就等于 +`Min(1, 1, 3) + 1`。 + +![221.maximal-square](../assets/problems/221.maximal-square-2.jpg) + +dp[i - 1][j - 1]我们直接拿到,关键是`往上和往左进行延伸`, 最直观的做法是我们内层加一个循环去做就好了。 +但是我们仔细观察一下,其实我们根本不需要这样算。 我们可以直接用dp[i - 1][j]和dp[i][j -1]。 +具体就是`Min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1`。 + +![221.maximal-square](../assets/problems/221.maximal-square-3.jpg) + +事实上,这道题还有空间复杂度O(N)的解法,其中N指的是列数。 +大家可以去这个[leetcode讨论](https://leetcode.com/problems/maximal-square/discuss/61803/C%2B%2B-space-optimized-DP)看一下。 +## 关键点解析 + +- DP +- 递归公式可以利用dp[i - 1][j]和dp[i][j -1]的计算结果,而不用重新计算 +- 空间复杂度可以降低到O(n), n为列数 + +## 代码 + +代码支持:Python,JavaScript: + +Python Code: + +```python +class Solution: + def maximalSquare(self, matrix: List[List[str]]) -> int: + res = 0 + m = len(matrix) + if m == 0: + return 0 + n = len(matrix[0]) + dp = [[0] * (n + 1) for _ in range(m + 1)] + + for i in range(1, m + 1): + for j in range(1, n + 1): + dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1 if matrix[i - 1][j - 1] == "1" else 0 + res = max(res, dp[i][j]) + return res ** 2 +``` + + +JavaScript Code: + +```js + +/* + * @lc app=leetcode id=221 lang=javascript + * + * [221] Maximal Square + */ +/** + * @param {character[][]} matrix + * @return {number} + */ +var maximalSquare = function(matrix) { + if (matrix.length === 0) return 0; + const dp = []; + const rows = matrix.length; + const cols = matrix[0].length; + let max = Number.MIN_VALUE; + + for (let i = 0; i < rows + 1; i++) { + if (i === 0) { + dp[i] = Array(cols + 1).fill(0); + } else { + dp[i] = [0]; + } + } + + for (let i = 1; i < rows + 1; i++) { + for (let j = 1; j < cols + 1; j++) { + if (matrix[i - 1][j - 1] === "1") { + dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1; + max = Math.max(max, dp[i][j]); + } else { + dp[i][j] = 0; + } + } + } + + return max * max; +}; +``` + + +***复杂度分析*** + +- 时间复杂度:$O(M * N)$,其中M为行数,N为列数。 +- 空间复杂度:$O(M * N)$,其中M为行数,N为列数。 diff --git a/problems/226.invert-binary-tree.md b/problems/226.invert-binary-tree.md new file mode 100644 index 0000000..664bcf1 --- /dev/null +++ b/problems/226.invert-binary-tree.md @@ -0,0 +1,183 @@ + +## 题目地址 +https://leetcode.com/problems/invert-binary-tree/description/ + +## 题目描述 + +``` +Invert a binary tree. + +Example: + +Input: + + 4 + / \ + 2 7 + / \ / \ +1 3 6 9 +Output: + + 4 + / \ + 7 2 + / \ / \ +9 6 3 1 +Trivia: +This problem was inspired by this original tweet by Max Howell: + +Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so f*** off. +``` + +## 思路 +遍历树(随便怎么遍历),然后将左右子树交换位置。 +## 关键点解析 + +- 递归简化操作 +- 如果树很高,建议使用栈来代替递归 +- 这道题目对顺序没要求的,因此队列数组操作都是一样的,无任何区别 + +## 代码 + +* 语言支持:JS,Python,C++ + +Javascript Code: + +```js +/* + * @lc app=leetcode id=226 lang=javascript + * + * [226] Invert Binary Tree + * + * https://leetcode.com/problems/invert-binary-tree/description/ + * + * algorithms + * Easy (57.14%) + * Total Accepted: 311K + * Total Submissions: 540.6K + * Testcase Example: '[4,2,7,1,3,6,9]' + * + * Invert a binary tree. + * + * Example: + * + * Input: + * + * + * ⁠ 4 + * ⁠ / \ + * ⁠ 2 7 + * ⁠/ \ / \ + * 1 3 6 9 + * + * Output: + * + * + * ⁠ 4 + * ⁠ / \ + * ⁠ 7 2 + * ⁠/ \ / \ + * 9 6 3 1 + * + * Trivia: + * This problem was inspired by this original tweet by Max Howell: + * + * Google: 90% of our engineers use the software you wrote (Homebrew), but you + * can’t invert a binary tree on a whiteboard so f*** off. + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {TreeNode} + */ +var invertTree = function(root) { + if (!root) return root; + // 递归 + // const left = root.left; + // const right = root.right; + // root.right = invertTree(left); + // root.left = invertTree(right); + // 我们用stack来模拟递归 + // 本质上递归是利用了执行栈,执行栈也是一种栈 + // 其实这里使用队列也是一样的,因为这里顺序不重要 + + const stack = [root]; + let current = null; + while ((current = stack.shift())) { + const left = current.left; + const right = current.right; + current.right = left; + current.left = right; + if (left) { + stack.push(left); + } + if (right) { + stack.push(right); + } + } + return root; +}; +``` + +Python Code: + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def invertTree(self, root: TreeNode) -> TreeNode: + if not root: + return None + stack = [root] + while stack: + node = stack.pop(0) + node.left, node.right = node.right, node.left + if node.left: + stack.append(node.left) + if node.right: + stack.append(node.right) + return root +``` +C++ Code: +```C++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +public: + TreeNode* invertTree(TreeNode* root) { + if (root == NULL) return root; + auto q = queue(); + q.push(root); + while (!q.empty()) { + auto n = q.front(); q.pop(); + swap(n->left, n->right); + if (n->left != nullptr) { + q.push(n->left); + } + if (n->right != nullptr) { + q.push(n->right); + } + } + return root; + } +}; +``` diff --git a/problems/229.majority-element-ii.md b/problems/229.majority-element-ii.md new file mode 100644 index 0000000..bbe4b64 --- /dev/null +++ b/problems/229.majority-element-ii.md @@ -0,0 +1,167 @@ + +## 题目地址 +https://leetcode.com/problems/majority-element-ii/description/ + +## 题目描述 + +``` +Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times. + +Note: The algorithm should run in linear time and in O(1) space. + +Example 1: + +Input: [3,2,3] +Output: [3] +Example 2: + +Input: [1,1,1,3,3,2,2,2] +Output: [1,2] +``` + +## 思路 + +这道题目和[169.majority-element](./169.majority-element.md) 很像。 + +我们仍然可以采取同样的方法 - “摩尔投票法”, 具体的思路可以参考上面的题目。 + +但是这里有一个不同的是这里的众数不再是超过`1 / 2`,而是超过`1 / 3`。 +题目也说明了,超过三分之一的有可能有多个(实际上就是0,1,2三种可能)。 + +因此我们不能只用一个counter来解决了。 我们的思路是同时使用两个counter,其他思路和上一道题目一样。 + +最后需要注意的是两个counter不一定都满足条件,这两个counter只是出现次数最多的两个数字。 +有可能不满足出现次数大于1/3, 因此最后我们需要进行过滤筛选。 + +这里画了一个图,大家可以感受一下: + +![229.majority-element-ii-1](../assets/problems/229.majority-element-ii-1.jpeg) + +![229.majority-element-ii-1](../assets/problems/229.majority-element-ii-2.jpeg) + + + +## 关键点解析 + +- 摩尔投票法 +- 两个counter +- 最后得到的只是出现次数最多的两个数字,有可能不满足出现次数大于1/3 + +## 代码 + +JavaScript代码: + +```js +/* + * @lc app=leetcode id=229 lang=javascript + * + * [229] Majority Element II + */ +/** + * @param {number[]} nums + * @return {number[]} + */ +var majorityElement = function(nums) { + const res = []; + const len = nums.length; + let n1 = null, + n2 = null, + cnt1 = 0, + cnt2 = 0; + + for (let i = 0; i < len; i++) { + if (n1 === nums[i]) { + cnt1++; + } else if (n2 === nums[i]) { + cnt2++; + } else if (cnt1 === 0) { + n1 = nums[i]; + cnt1++; + } else if (cnt2 === 0) { + n2 = nums[i]; + cnt2++; + } else { + cnt1--; + cnt2--; + } + } + + cnt1 = 0; + cnt2 = 0; + + for (let i = 0; i < len; i++) { + if (n1 === nums[i]) { + cnt1++; + } else if (n2 === nums[i]) { + cnt2++; + } + } + + if (cnt1 > (len / 3) >>> 0) { + res.push(n1); + } + if (cnt2 > (len / 3) >>> 0) { + res.push(n2); + } + + return res; +}; + +``` + +Java代码: + +```java +/* + * @lc app=leetcode id=229 lang=java + * + * [229] Majority Element II + */ +class Solution { + public List majorityElement(int[] nums) { + List res = new ArrayList(); + if (nums == null || nums.length == 0) + return res; + int n1 = nums[0], n2 = nums[0], cnt1 = 0, cnt2 = 0, len = nums.length; + for (int i = 0; i < len; i++) { + if (nums[i] == n1) + cnt1++; + else if (nums[i] == n2) + cnt2++; + else if (cnt1 == 0) { + n1 = nums[i]; + cnt1 = 1; + } else if (cnt2 == 0) { + n2 = nums[i]; + cnt2 = 1; + } else { + cnt1--; + cnt2--; + } + } + cnt1 = 0; + cnt2 = 0; + for (int i = 0; i < len; i++) { + if (nums[i] == n1) + cnt1++; + else if (nums[i] == n2) + cnt2++; + } + if (cnt1 > len / 3) + res.add(n1); + if (cnt2 > len / 3 && n1 != n2) + res.add(n2); + return res; + } +} + +``` + + +## 扩展 + +如果题目中3变成了k,怎么解决? + +大家可以自己思考一下,我这里给一个参考链接:https://leetcode.com/problems/majority-element-ii/discuss/63500/JAVA-Easy-Version-To-Understand!!!!!!!!!!!!/64925 + +这个实现说实话不是很好,大家可以优化一下。 diff --git a/problems/23.merge-k-sorted-lists.md b/problems/23.merge-k-sorted-lists.md new file mode 100644 index 0000000..ba72dd0 --- /dev/null +++ b/problems/23.merge-k-sorted-lists.md @@ -0,0 +1,162 @@ +## 题目地址(23. 合并 K 个排序链表) + +https://leetcode-cn.com/problems/merge-k-sorted-lists/description/ + +## 题目描述 + +合并  k  个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。 + +示例: + +输入: +[ +  1->4->5, +  1->3->4, +  2->6 +] +输出: 1->1->2->3->4->4->5->6 + +## 思路 + +这道题目是合并 k 个已排序的链表,号称 leetcode 目前`最难`的链表题。 和之前我们解决的[88.merge-sorted-array](./88.merge-sorted-array.md)很像。 +他们有两点区别: + +1. 这道题的数据结构是链表,那道是数组。这个其实不复杂,毕竟都是线性的数据结构。 + +2. 这道题需要合并 k 个元素,那道则只需要合并两个。这个是两题的关键差别,也是这道题难度为`hard`的原因。 + +因此我们可以看出,这道题目是`88.merge-sorted-array`的进阶版本。其实思路也有点像,我们来具体分析下第二条。 +如果你熟悉合并排序的话,你会发现它就是`合并排序的一部分`。 + +具体我们可以来看一个动画 + +![23.merge-k-sorted-lists](../assets/problems/23.merge-k-sorted-lists.gif) + +(动画来自 https://zhuanlan.zhihu.com/p/61796021 ) + +## 关键点解析 + +- 分治 +- 归并排序(merge sort) + +## 代码 + +代码支持 JavaScript, Python3 + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=23 lang=javascript + * + * [23] Merge k Sorted Lists + * + * https://leetcode.com/problems/merge-k-sorted-lists/description/ + * + */ +function mergeTwoLists(l1, l2) { + const dummyHead = {}; + let current = dummyHead; + // l1: 1 -> 3 -> 5 + // l2: 2 -> 4 -> 6 + while (l1 !== null && l2 !== null) { + if (l1.val < l2.val) { + current.next = l1; // 把小的添加到结果链表 + current = current.next; // 移动结果链表的指针 + l1 = l1.next; // 移动小的那个链表的指针 + } else { + current.next = l2; + current = current.next; + l2 = l2.next; + } + } + + if (l1 === null) { + current.next = l2; + } else { + current.next = l1; + } + return dummyHead.next; +} +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode[]} lists + * @return {ListNode} + */ +var mergeKLists = function(lists) { + // 图参考: https://zhuanlan.zhihu.com/p/61796021 + if (lists.length === 0) return null; + if (lists.length === 1) return lists[0]; + if (lists.length === 2) { + return mergeTwoLists(lists[0], lists[1]); + } + + const mid = lists.length >> 1; + const l1 = []; + for (let i = 0; i < mid; i++) { + l1[i] = lists[i]; + } + + const l2 = []; + for (let i = mid, j = 0; i < lists.length; i++, j++) { + l2[j] = lists[i]; + } + + return mergeTwoLists(mergeKLists(l1), mergeKLists(l2)); +}; +``` + +Python3 Code: + +``` python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def mergeKLists(self, lists: List[ListNode]) -> ListNode: + n = len(lists) + + # basic cases + if lenth == 0: return None + if lenth == 1: return lists[0] + if lenth == 2: return self.mergeTwoLists(lists[0], lists[1]) + + # divide and conqure if not basic cases + mid = n // 2 + return self.mergeTwoLists(self.mergeKLists(lists[:mid]), self.mergeKLists(lists[mid:n])) + + + def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: + res = ListNode(0) + c1, c2, c3 = l1, l2, res + while c1 or c2: + if c1 and c2: + if c1.val < c2.val: + c3.next = ListNode(c1.val) + c1 = c1.next + else: + c3.next = ListNode(c2.val) + c2 = c2.next + c3 = c3.next + elif c1: + c3.next = c1 + break + else: + c3.next = c2 + break + + return res.next +``` + +## 相关题目 + +- [88.merge-sorted-array](./88.merge-sorted-array.md) diff --git a/problems/230.kth-smallest-element-in-a-bst.md b/problems/230.kth-smallest-element-in-a-bst.md new file mode 100644 index 0000000..b11e07d --- /dev/null +++ b/problems/230.kth-smallest-element-in-a-bst.md @@ -0,0 +1,189 @@ +## 题目地址 + +https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/ + +## 题目描述 + +``` +Given a binary search tree, write a function kthSmallest to find the kth smallest element in it. + +Note: +You may assume k is always valid, 1 ≤ k ≤ BST's total elements. + +Example 1: + +Input: root = [3,1,4,null,2], k = 1 + 3 + / \ + 1 4 + \ + 2 +Output: 1 +Example 2: + +Input: root = [5,3,6,2,4,null,null,1], k = 3 + 5 + / \ + 3 6 + / \ + 2 4 + / + 1 +Output: 3 +Follow up: +What if the BST is modified (insert/delete operations) often and you need to find the kth smallest frequently? How would you optimize the kthSmallest routine? + +``` + +## 思路 + +解法一: + +由于‘中序遍历一个二叉查找树(BST)的结果是一个有序数组’ ,因此我们只需要在遍历到第k个,返回当前元素即可。 +中序遍历相关思路请查看[binary-tree-traversal](../thinkings/binary-tree-traversal.md) + +解法二: + +联想到二叉搜索树的性质,root 大于左子树,小于右子树,如果左子树的节点数目等于 K-1,那么 root 就是结果,否则如果左子树节点数目小于 K-1,那么结果必然在右子树,否则就在左子树。 +因此在搜索的时候同时返回节点数目,跟 K 做对比,就能得出结果了。 + + +## 关键点解析 + +- 中序遍历 + +## 代码 + +解法一: + +JavaScript Code: + +```js + + +/* + * @lc app=leetcode id=230 lang=javascript + * + * [230] Kth Smallest Element in a BST + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @param {number} k + * @return {number} + */ +var kthSmallest = function(root, k) { + const stack = [root]; + let cur = root; + let i = 0; + + function insertAllLefts(cur) { + while(cur && cur.left) { + const l = cur.left; + stack.push(l); + cur = l; + } + } + insertAllLefts(cur); + + while(cur = stack.pop()) { + i++; + if (i === k) return cur.val; + const r = cur.right; + + if (r) { + stack.push(r); + insertAllLefts(r); + } + } + + return -1; + + +}; +``` + +Java Code: + +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +private int count = 1; +private int res; + +public int KthSmallest (TreeNode root, int k) { + inorder(root, k); + return res; +} + +public void inorder (TreeNode root, int k) { + if (root == null) return; + + inorder(root.left, k); + + if (count++ == k) { + res = root.val; + return; + } + + inorder(root.right, k); +} +``` + +解法二: + +JavaScript Code: + +```js + +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function nodeCount(node) { + if (node === null) return 0; + + const l = nodeCount(node.left); + const r = nodeCount(node.right); + + return 1 + l + r; +} +/** + * @param {TreeNode} root + * @param {number} k + * @return {number} + */ +var kthSmallest = function(root, k) { + const c = nodeCount(root.left); + if (c === k - 1) return root.val; + else if (c < k - 1) return kthSmallest(root.right, k - c - 1); + return kthSmallest(root.left, k) +}; + +``` + +## 扩展 + +这道题有一个follow up: + +`What if the BST is modified (insert/delete operations) often and you need to find the kth smallest frequently? + How would you optimize the kthSmallest routine?` + +大家可以思考一下。 + diff --git a/problems/232.implement-queue-using-stacks.md b/problems/232.implement-queue-using-stacks.md new file mode 100644 index 0000000..d5f4b1e --- /dev/null +++ b/problems/232.implement-queue-using-stacks.md @@ -0,0 +1,252 @@ +## 题目地址 + +https://leetcode.com/problems/implement-queue-using-stacks/description/ + +## 题目描述 + +``` +Implement the following operations of a queue using stacks. + +push(x) -- Push element x to the back of queue. +pop() -- Removes the element from in front of queue. +peek() -- Get the front element. +empty() -- Return whether the queue is empty. +Example: + +MyQueue queue = new MyQueue(); + +queue.push(1); +queue.push(2); +queue.peek(); // returns 1 +queue.pop(); // returns 1 +queue.empty(); // returns false +Notes: + +You must use only standard operations of a stack -- which means only push to top, peek/pop from top, size, and is empty operations are valid. +Depending on your language, stack may not be supported natively. You may simulate a stack by using a list or deque (double-ended queue), as long as you use only standard operations of a stack. +You may assume that all operations are valid (for example, no pop or peek operations will be called on an empty queue). +``` + +## 思路 + +这道题目是让我们用栈来模拟实现队列。 我们知道栈和队列都是一种受限的数据结构。 +栈的特点是只能在一端进行所有操作,队列的特点是只能在一端入队,另一端出队。 + +在这里我们可以借助另外一个栈,也就是说用两个栈来实现队列的效果。这种做法的时间复杂度和空间复杂度都是O(n)。 + +由于栈只能操作一端,因此我们peek或者pop的时候也只去操作顶部元素,要达到目的 +我们需要在push的时候将队头的元素放到栈顶即可。 + +因此我们只需要在push的时候,用一下辅助栈即可。 +具体做法是先将栈清空并依次放到另一个辅助栈中,辅助栈中的元素再次放回栈中,最后将新的元素push进去即可。 + +比如我们现在栈中已经是1,2,3,4了。 我们现在要push一个5. + +push之前是这样的: + +![232.implement-queue-using-stacks.drawio](../assets/problems/232.implement-queue-using-stacks-1.jpg) + +然后我们将栈中的元素转移到辅助栈: + +![232.implement-queue-using-stacks.drawio](../assets/problems/232.implement-queue-using-stacks-2.jpg) + +最后将新的元素添加到栈顶。 + +![232.implement-queue-using-stacks.drawio](../assets/problems/232.implement-queue-using-stacks-3.jpg) + + +整个过程是这样的: + +![232.implement-queue-using-stacks.drawio](../assets/problems/232.implement-queue-using-stacks-4.jpg) +## 关键点解析 + +- 在push的时候利用辅助栈(双栈) + +## 代码 + +* 语言支持:JS, Python, Java + +Javascript Code: + +```js +/* + * @lc app=leetcode id=232 lang=javascript + * + * [232] Implement Queue using Stacks + */ +/** + * Initialize your data structure here. + */ +var MyQueue = function() { + // tag: queue stack array + this.stack = []; + this.helperStack = []; +}; + +/** + * Push element x to the back of queue. + * @param {number} x + * @return {void} + */ +MyQueue.prototype.push = function(x) { + let cur = null; + while ((cur = this.stack.pop())) { + this.helperStack.push(cur); + } + this.helperStack.push(x); + + while ((cur = this.helperStack.pop())) { + this.stack.push(cur); + } +}; + +/** + * Removes the element from in front of queue and returns that element. + * @return {number} + */ +MyQueue.prototype.pop = function() { + return this.stack.pop(); +}; + +/** + * Get the front element. + * @return {number} + */ +MyQueue.prototype.peek = function() { + return this.stack[this.stack.length - 1]; +}; + +/** + * Returns whether the queue is empty. + * @return {boolean} + */ +MyQueue.prototype.empty = function() { + return this.stack.length === 0; +}; + +/** + * Your MyQueue object will be instantiated and called as such: + * var obj = new MyQueue() + * obj.push(x) + * var param_2 = obj.pop() + * var param_3 = obj.peek() + * var param_4 = obj.empty() + */ +``` + +Python Code: + +```python +class MyQueue: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.stack = [] + self.help_stack = [] + + def push(self, x: int) -> None: + """ + Push element x to the back of queue. + """ + while self.stack: + self.help_stack.append(self.stack.pop()) + self.help_stack.append(x) + while self.help_stack: + self.stack.append(self.help_stack.pop()) + + def pop(self) -> int: + """ + Removes the element from in front of queue and returns that element. + """ + return self.stack.pop() + + def peek(self) -> int: + """ + Get the front element. + """ + return self.stack[-1] + + def empty(self) -> bool: + """ + Returns whether the queue is empty. + """ + return not bool(self.stack) + + +# Your MyQueue object will be instantiated and called as such: +# obj = MyQueue() +# obj.push(x) +# param_2 = obj.pop() +# param_3 = obj.peek() +# param_4 = obj.empty() +``` + +Java Code + +```java +class MyQueue { + Stack pushStack = new Stack<> (); + Stack popStack = new Stack<> (); + + /** Initialize your data structure here. */ + public MyQueue() { + + } + + /** Push element x to the back of queue. */ + public void push(int x) { + while (!popStack.isEmpty()) { + pushStack.push(popStack.pop()); + } + pushStack.push(x); + } + + /** Removes the element from in front of queue and returns that element. */ + public int pop() { + while (!pushStack.isEmpty()) { + popStack.push(pushStack.pop()); + } + return popStack.pop(); + } + + /** Get the front element. */ + public int peek() { + while (!pushStack.isEmpty()) { + popStack.push(pushStack.pop()); + } + return popStack.peek(); + } + + /** Returns whether the queue is empty. */ + public boolean empty() { + return pushStack.isEmpty() && popStack.isEmpty(); + } +} + +/** + * Your MyQueue object will be instantiated and called as such: + * MyQueue obj = new MyQueue(); + * obj.push(x); + * int param_2 = obj.pop(); + * int param_3 = obj.peek(); + * boolean param_4 = obj.empty(); + */ +```` + +## 扩展 + - 类似的题目有用队列实现栈,思路是完全一样的,大家有兴趣可以试一下。 + - 栈混洗也是借助另外一个栈来完成的,从这点来看,两者有相似之处。 + +## 延伸阅读 + +实际上现实中也有使用两个栈来实现队列的情况,那么为什么我们要用两个stack来实现一个queue? + +其实使用两个栈来替代一个队列的实现是为了在多进程中分开对同一个队列对读写操作。一个栈是用来读的,另一个是用来写的。当且仅当读栈满时或者写栈为空时,读写操作才会发生冲突。 + +当只有一个线程对栈进行读写操作的时候,总有一个栈是空的。在多线程应用中,如果我们只有一个队列,为了线程安全,我们在读或者写队列的时候都需要锁住整个队列。而在两个栈的实现中,只要写入栈不为空,那么`push`操作的锁就不会影响到`pop`。 + +- [reference](https://leetcode.com/problems/implement-queue-using-stacks/discuss/64284/Do-you-know-when-we-should-use-two-stacks-to-implement-a-queue) + +- [further reading](https://stackoverflow.com/questions/2050120/why-use-two-stacks-to-make-a-queue/2050402#2050402) diff --git a/problems/236.lowest-common-ancestor-of-a-binary-tree.md b/problems/236.lowest-common-ancestor-of-a-binary-tree.md new file mode 100644 index 0000000..f5a85d6 --- /dev/null +++ b/problems/236.lowest-common-ancestor-of-a-binary-tree.md @@ -0,0 +1,129 @@ +## 题目地址 + +https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/ + +## 题目描述 + +``` +Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. + +According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).” + +Given the following binary tree: root = [3,5,1,6,2,0,8,null,null,7,4] + +``` +![236.lowest-common-ancestor-of-a-binary-tree](../assets/problems/236.lowest-common-ancestor-of-a-binary-tree-1.png) + +``` +Example 1: + +Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 +Output: 3 +Explanation: The LCA of nodes 5 and 1 is 3. +Example 2: + +Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 +Output: 5 +Explanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition. + + +Note: + +All of the nodes' values will be unique. +p and q are different and both values will exist in the binary tree. +``` + +## 思路 + +这道题目是求解二叉树中,两个给定节点的最近的公共祖先。是一道非常经典的二叉树题目。 + +我们之前说过树是一种递归的数据结构,因此使用递归方法解决二叉树问题从写法上来看是最简单的,这道题目也不例外。 + +用递归的思路去思考树是一种非常重要的能力。 + + +如果大家这样去思考的话,问题就会得到简化,我们的目标就是分别在左右子树进行查找p和q。 如果p没有在左子树,那么它一定在右子树(题目限定p一定在树中), +反之亦然。 + +对于具体的代码而言就是,我们假设这个树就一个结构,然后尝试去解决,然后在适当地方去递归自身即可。 如下图所示: + +![236.lowest-common-ancestor-of-a-binary-tree-2](../assets/problems/236.lowest-common-ancestor-of-a-binary-tree-2.png) + +我们来看下核心代码: + +```js + // 如果我们找到了p,直接进行返回,那如果下面就是q呢? 其实这没有影响,但是还是要多考虑一下 + if (!root || root === p || root === q) return root; + const left = lowestCommonAncestor(root.left, p, q); // 去左边找,我们期望返回找到的节点 + const right = lowestCommonAncestor(root.right, p, q);// 去右边找,我们期望返回找到的节点 + if (!left) return right; // 左子树找不到,返回右子树 + if (!right) return left; // 右子树找不到,返回左子树 + return root; // 左右子树分别有一个,则返回root + +``` + +> 如果没有明白的话,请多花时间消化一下 + +## 关键点解析 + +- 用递归的思路去思考树 + +## 代码 + +代码支持: JavaScript, Python3 + +- JavaScript Code: + +```js +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @param {TreeNode} p + * @param {TreeNode} q + * @return {TreeNode} + */ +var lowestCommonAncestor = function(root, p, q) { + if (!root || root === p || root === q) return root; + const left = lowestCommonAncestor(root.left, p, q); + const right = lowestCommonAncestor(root.right, p, q); + if (!left) return right; // 左子树找不到,返回右子树 + if (!right) return left; // 右子树找不到,返回左子树 + return root; // 左右子树分别有一个,则返回root +}; +``` + +- Python Code: + +``` python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + if not root or root == p or root == q: + return root + left = self.lowestCommonAncestor(root.left, p, q) + right = self.lowestCommonAncestor(root.right, p, q) + + if not left: + return right + if not right: + return left + else: + return root + +``` + +## 扩展 +如果递归的结束条件改为`if (!root || root.left === p || root.right === q) return root;` 代表的是什么意思,对结果有什么样的影响? + diff --git a/problems/238.product-of-array-except-self.md b/problems/238.product-of-array-except-self.md new file mode 100644 index 0000000..70f5a01 --- /dev/null +++ b/problems/238.product-of-array-except-self.md @@ -0,0 +1,102 @@ +## 题目地址 + +https://leetcode.com/problems/product-of-array-except-self/description/ + +## 题目描述 + +``` +Given an array nums of n integers where n > 1, return an array output such that output[i] is equal to the product of all the elements of nums except nums[i]. + +Example: + +Input: [1,2,3,4] +Output: [24,12,8,6] +Note: Please solve it without division and in O(n). + +Follow up: +Could you solve it with constant space complexity? (The output array does not count as extra space for the purpose of space complexity analysis.) + + +``` + +## 思路 + +这道题的意思是给定一个数组,返回一个新的数组,这个数组每一项都是其他项的乘积。 +符合直觉的思路是两层循环,时间复杂度是O(n^2),但是题目要求`Please solve it without division and in O(n)`。 + +因此我们需要换一种思路,由于输出的每一项都需要用到别的元素,因此一次遍历是绝对不行的。 +考虑我们先进行一次遍历, 然后维护一个数组,第i项代表前i个元素(不包括i)的乘积。 +然后我们反向遍历一次,然后维护另一个数组,同样是第i项代表前i个元素(不包括i)的乘积。 + +![238.product-of-array-except-self](../assets/problems/238.product-of-array-except-self.png) + +有意思的是第一个数组和第二个数组的反转(reverse)做乘法(有点像向量运算)就是我们想要的运算。 + +其实我们进一步观察,我们不需要真的创建第二个数组(第二个数组只是做中间运算使用),而是直接修改第一个数组即可。 + +## 关键点解析 + +- 两次遍历, 一次正向,一次反向。 +- 维护一个数组,第i项代表前i个元素(不包括i)的乘积 + +## 代码 + +```js + +/* + * @lc app=leetcode id=238 lang=javascript + * + * [238] Product of Array Except Self + * + * https://leetcode.com/problems/product-of-array-except-self/description/ + * + * algorithms + * Medium (53.97%) + * Total Accepted: 246.5K + * Total Submissions: 451.4K + * Testcase Example: '[1,2,3,4]' + * + * Given an array nums of n integers where n > 1,  return an array output such + * that output[i] is equal to the product of all the elements of nums except + * nums[i]. + * + * Example: + * + * + * Input: [1,2,3,4] + * Output: [24,12,8,6] + * + * + * Note: Please solve it without division and in O(n). + * + * Follow up: + * Could you solve it with constant space complexity? (The output array does + * not count as extra space for the purpose of space complexity analysis.) + * + */ +/** + * @param {number[]} nums + * @return {number[]} + */ +var productExceptSelf = function(nums) { + const ret = []; + + for (let i = 0, temp = 1; i < nums.length; i++) { + ret[i] = temp; + temp *= nums[i]; + } + // 此时ret[i]存放的是前i个元素相乘的结果(不包含第i个) + + // 如果没有上面的循环的话, + // ret经过下面的循环会变成ret[i]存放的是后i个元素相乘的结果(不包含第i个) + + // 我们的目标是ret[i]存放的所有数字相乘的结果(不包含第i个) + + // 因此我们只需要对于上述的循环产生的ret[i]基础上运算即可 + for (let i = nums.length - 1, temp = 1; i >= 0; i--) { + ret[i] *= temp; + temp *= nums[i]; + } + return ret; +}; +``` diff --git a/problems/239.sliding-window-maximum.md b/problems/239.sliding-window-maximum.md new file mode 100644 index 0000000..f45f39c --- /dev/null +++ b/problems/239.sliding-window-maximum.md @@ -0,0 +1,142 @@ +## 题目地址 + +https://leetcode.com/problems/sliding-window-maximum/description/ + +## 题目描述 + +``` +Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Return the max sliding window. + +Example: + +Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3 +Output: [3,3,5,5,6,7] +Explanation: + +Window position Max +--------------- ----- +[1 3 -1] -3 5 3 6 7 3 + 1 [3 -1 -3] 5 3 6 7 3 + 1 3 [-1 -3 5] 3 6 7 5 + 1 3 -1 [-3 5 3] 6 7 5 + 1 3 -1 -3 [5 3 6] 7 6 + 1 3 -1 -3 5 [3 6 7] 7 +Note: +You may assume k is always valid, 1 ≤ k ≤ input array's size for non-empty array. + +Follow up: +Could you solve it in linear time? +``` + +## 思路 + +符合直觉的想法是直接遍历 nums, 然后然后用一个变量 slideWindow 去承载 k 个元素, +然后对 slideWindow 求最大值,这是可以的,时间复杂度是 O(n \* k).代码如下: + +JavaScript: + +```js +var maxSlidingWindow = function(nums, k) { + // bad 时间复杂度O(n * k) + if (nums.length === 0 || k === 0) return []; + let slideWindow = []; + const ret = []; + for (let i = 0; i < nums.length - k + 1; i++) { + for (let j = 0; j < k; j++) { + slideWindow.push(nums[i + j]); + } + ret.push(Math.max(...slideWindow)); + slideWindow = []; + } + return ret; +}; +``` +Python3: + +```python +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: + if k == 0: return [] + res = [] + for r in range(k - 1, len(nums)): + res.append(max(nums[r - k + 1:r + 1])) + return res +``` + +但是如果真的是这样,这道题也不会是 hard 吧?这道题有一个 follow up,要求你用线性的时间去完成。 +我们可以用双端队列来完成,思路是用一个双端队列来保存`接下来的滑动窗口可能成为最大值的数`。具体做法: + + +- 入队列 + +- 移除失效元素,失效元素有两种 + +1. 一种是已经超出窗口范围了,比如我遍历到第4个元素,k = 3,那么i = 0的元素就不应该出现在双端队列中了 +具体就是`索引大于 i - k + 1的元素都应该被清除` + +2. 小于当前元素都没有利用价值了,具体就是`从后往前遍历(双端队列是一个递减队列)双端队列,如果小于当前元素就出队列` + + +如果你仔细观察的话,发现双端队列其实是一个递减的一个队列。因此队首的元素一定是最大的。用图来表示就是: + +![](https://tva1.sinaimg.cn/large/0082zybply1gbvyn8ufbvj30hb0di75s.jpg) + +## 关键点解析 + +- 双端队列简化时间复杂度 + +- 滑动窗口 + +## 代码 + + +JavaScript: + +```js +var maxSlidingWindow = function(nums, k) { + // 双端队列优化时间复杂度, 时间复杂度O(n) + const deque = []; // 存放在接下来的滑动窗口可能成为最大值的数 + const ret = []; + for (let i = 0; i < nums.length; i++) { + // 清空失效元素 + while (deque[0] < i - k + 1) { + deque.shift(); + } + + while (nums[deque[deque.length - 1]] < nums[i]) { + deque.pop(); + } + + deque.push(i); + + if (i >= k - 1) { + ret.push(nums[deque[0]]); + } + } + return ret; +}; +``` + +Python3: + +```python +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: + deque, res, n = [], [], len(nums) + for i in range(n): + while deque and deque[0] < i - k + 1: + deque.pop(0) + while deque and nums[i] > nums[deque[-1]]: + deque.pop(-1) + deque.append(i) + if i >= k - 1: res.append(nums[deque[0]]) + return res + + +``` + +## 扩展 + +### 为什么用双端队列 +因为删除无效元素的时候,会清除队首的元素(索引太小了 +)或者队尾(元素太小了)的元素。 因此需要同时对队首和队尾进行操作,使用双端队列是一种合乎情理的做法。 diff --git a/problems/24.swapNodesInPairs.md b/problems/24.swapNodesInPairs.md new file mode 100644 index 0000000..368a353 --- /dev/null +++ b/problems/24.swapNodesInPairs.md @@ -0,0 +1,94 @@ +## 题目地址 +https://leetcode.com/problems/swap-nodes-in-pairs/description/ + +## 题目描述 +Given a linked list, swap every two adjacent nodes and return its head. + +You may not modify the values in the list's nodes, only nodes itself may be changed. + + + +Example: + +Given 1->2->3->4, you should return the list as 2->1->4->3. +## 思路 + +设置一个 dummy 节点简化操作,dummy next 指向 head。 + +1. 初始化 first 为第一个节点 +2. 初始化 second 为第二个节点 +3. 初始化 current 为 dummy +4. first.next = second.next +5. second.next = first +6. current.next = second +7. current 移动两格 +8. 重复 + +![24.swap-nodes-in-pairs](../assets/24.swap-nodes-in-pairs.gif) + +(图片来自: https://github.com/MisterBooo/LeetCodeAnimation) + +## 关键点解析 + +1. 链表这种数据结构的特点和使用 + +2. dummyHead 简化操作 + +## 代码 + +* 语言支持:JS,Python3 + +```js +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ +var swapPairs = function(head) { + const dummy = new ListNode(0); + dummy.next = head; + let current = dummy; + while (current.next != null && current.next.next != null) { + // 初始化双指针 + const first = current.next; + const second = current.next.next; + + // 更新双指针和 current 指针 + first.next = second.next; + second.next = first; + current.next = second; + + // 更新指针 + current = current.next.next; + } + return dummy.next; +}; + +``` +Python3 Code: +```python +class Solution: + def swapPairs(self, head: ListNode) -> ListNode: + """ + 用递归实现链表相邻互换: + 第一个节点的 next 是第三、第四个节点交换的结果,第二个节点的 next 是第一个节点; + 第三个节点的 next 是第五、第六个节点交换的结果,第四个节点的 next 是第三个节点; + 以此类推 + :param ListNode head + :return ListNode + """ + # 如果为 None 或 next 为 None,则直接返回 + if not head or not head.next: + return head + + _next = head.next + head.next = self.swapPairs(_next.next) + _next.next = head + return _next +``` diff --git a/problems/240.search-a-2-d-matrix-ii.md b/problems/240.search-a-2-d-matrix-ii.md new file mode 100644 index 0000000..8be96b8 --- /dev/null +++ b/problems/240.search-a-2-d-matrix-ii.md @@ -0,0 +1,112 @@ + +## 题目地址 +https://leetcode.com/problems/search-a-2d-matrix-ii/description/ + +## 题目描述 + +``` +Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties: + +Integers in each row are sorted in ascending from left to right. +Integers in each column are sorted in ascending from top to bottom. +Example: + +Consider the following matrix: + +[ + [1, 4, 7, 11, 15], + [2, 5, 8, 12, 19], + [3, 6, 9, 16, 22], + [10, 13, 14, 17, 24], + [18, 21, 23, 26, 30] +] +Given target = 5, return true. + +Given target = 20, return false. + +``` + +## 思路 + +符合直觉的做法是两层循环遍历,时间复杂度是O(m * n), +有没有时间复杂度更好的做法呢? 答案是有,那就是充分运用矩阵的特性(横向纵向都递增), +我们可以从角落(左下或者右上)开始遍历,这样时间复杂度是O(m + n). + +![](https://tva1.sinaimg.cn/large/0082zybply1gbrcf58gsqj30ft0b4wfv.jpg) + +其中蓝色代表我们选择的起点元素, 红色代表目标元素。 + +## 关键点解析 + +- 从角落开始遍历,利用递增的特性简化时间复杂度 + + +## 代码 + +代码支持:JavaScript, Python3 + + +JavaScript Code: + +```js + +/* + * @lc app=leetcode id=240 lang=javascript + * + * [240] Search a 2D Matrix II + * + * https://leetcode.com/problems/search-a-2d-matrix-ii/description/ + * + * + */ +/** + * @param {number[][]} matrix + * @param {number} target + * @return {boolean} + */ +var searchMatrix = function(matrix, target) { + if (!matrix || matrix.length === 0) return 0; + + let colIndex = 0; + let rowIndex = matrix.length - 1; + while(rowIndex > 0 && target < matrix[rowIndex][colIndex]) { + rowIndex --; + } + + while(colIndex < matrix[0].length) { + if (target === matrix[rowIndex][colIndex]) return true; + if (target > matrix[rowIndex][colIndex]) { + colIndex ++; + } else if (rowIndex > 0){ + rowIndex --; + } else { + return false; + } + } + + return false; +}; +``` + +Python Code: + +```python +class Solution: + def searchMatrix(self, matrix, target): + m = len(matrix) + if m == 0: + return False + n = len(matrix[0]) + i = m - 1 + j = 0 + + while i >= 0 and j < n: + if matrix[i][j] == target: + return True + if matrix[i][j] > target: + i -= 1 + else: + j += 1 + return False +``` + diff --git a/problems/25.reverse-nodes-in-k-groups-cn.md b/problems/25.reverse-nodes-in-k-groups-cn.md new file mode 100644 index 0000000..8286815 --- /dev/null +++ b/problems/25.reverse-nodes-in-k-groups-cn.md @@ -0,0 +1,249 @@ +## 题目地址 +https://leetcode.com/problems/reverse-nodes-in-k-group/ + +## 题目描述 +``` +Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. + +k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is. + +Example: + +Given this linked list: 1->2->3->4->5 + +For k = 2, you should return: 2->1->4->3->5 + +For k = 3, you should return: 3->2->1->4->5 + +Note: + +Only constant extra memory is allowed. +You may not alter the values in the list's nodes, only nodes itself may be changed. + +``` + +## 思路 +题意是以 `k` 个nodes为一组进行翻转,返回翻转后的`linked list`. + +从左往右扫描一遍`linked list`,扫描过程中,以k为单位把数组分成若干段,对每一段进行翻转。给定首尾nodes,如何对链表进行翻转。 + +链表的翻转过程,初始化一个为`null `的 `previous node(prev)`,然后遍历链表的同时,当前`node (curr)`的下一个(next)指向前一个`node(prev)`, +在改变当前node的指向之前,用一个临时变量记录当前node的下一个`node(curr.next)`. 即 +``` +ListNode temp = curr.next; +curr.next = prev; +prev = curr; +curr = temp; +``` + +举例如图:翻转整个链表 `1->2->3->4->null` -> `4->3->2->1->null` + +![reverse linked list](../assets/problems/25.reverse-nodes-in-k-groups-1.PNG) + +这里是对每一组(`k个nodes`)进行翻转, + +1. 先分组,用一个`count`变量记录当前节点的个数 + +2. 用一个`start` 变量记录当前分组的起始节点位置的前一个节点 + +3. 用一个`end `变量记录要翻转的最后一个节点位置 + +4. 翻转一组(`k个nodes`)即`(start, end) - start and end exclusively`。 + +5. 翻转后,`start`指向翻转后链表, 区间`(start,end)`中的最后一个节点, 返回`start` 节点。 + +6. 如果不需要翻转,`end` 就往后移动一个(`end=end.next`),每一次移动,都要`count+1`. + +如图所示 步骤4和5: 翻转区间链表区间`(start, end)` + +![reverse linked list range in (start, end)](../assets/problems/25.reverse-nodes-in-k-groups-3.png) + + +举例如图,`head=[1,2,3,4,5,6,7,8], k = 3` + + +![reverse k nodes in linked list](../assets/problems/25.reverse-nodes-in-k-groups-2.PNG) + + +>**NOTE**: 一般情况下对链表的操作,都有可能会引入一个新的`dummy node`,因为`head`有可能会改变。这里`head 从1->3`, +`dummy (List(0)) `保持不变。 + +#### 复杂度分析 +- *时间复杂度:* `O(n) - n is number of Linked List` +- *空间复杂度:* `O(1)` + +## 关键点分析 +1. 创建一个dummy node +2. 对链表以k为单位进行分组,记录每一组的起始和最后节点位置 +3. 对每一组进行翻转,更换起始和最后的位置 +4. 返回`dummy.next`. + +## 代码 (`Java/Python3/javascript`) +*Java Code* +```java +class ReverseKGroupsLinkedList { + public ListNode reverseKGroup(ListNode head, int k) { + if (head == null || k == 1) { + return head; + } + ListNode dummy = new ListNode(0); + dummy.next = head; + + ListNode start = dummy; + ListNode end = head; + int count = 0; + while (end != null) { + count++; + // group + if (count % k == 0) { + // reverse linked list (start, end] + start = reverse(start, end.next); + end = start.next; + } else { + end = end.next; + } + } + return dummy.next; + } + + /** + * reverse linked list from range (start, end), return last node. + * for example: + * 0->1->2->3->4->5->6->7->8 + * | | + * start end + * + * After call start = reverse(start, end) + * + * 0->3->2->1->4->5->6->7->8 + * | | + * start end + * first + * + */ + private ListNode reverse(ListNode start, ListNode end) { + ListNode curr = start.next; + ListNode prev = start; + ListNode first = curr; + while (curr != end){ + ListNode temp = curr.next; + curr.next = prev; + prev = curr; + curr = temp; + } + start.next = prev; + first.next = curr; + return first; + } +} +``` + +*Python3 Cose* +```python +class Solution: + def reverseKGroup(self, head: ListNode, k: int) -> ListNode: + if head is None or k < 2: + return head + dummy = ListNode(0) + dummy.next = head + start = dummy + end = head + count = 0 + while end: + count += 1 + if count % k == 0: + start = self.reverse(start, end.next) + end = start.next + else: + end = end.next + return dummy.next + + def reverse(self, start, end): + prev, curr = start, start.next + first = curr + while curr != end: + temp = curr.next + curr.next = prev + prev = curr + curr = temp + start.next = prev + first.next = curr + return first +``` + +*javascript code* +```js +/** + * @param {ListNode} head + * @param {number} k + * @return {ListNode} + */ +var reverseKGroup = function(head, k) { + // 标兵 + let dummy = new ListNode() + dummy.next = head + let [start, end] = [dummy, dummy.next] + let count = 0 + while(end) { + count++ + if (count % k === 0) { + start = reverseList(start, end.next) + end = start.next + } else { + end = end.next + } + } + return dummy.next + + // 翻转stat -> end的链表 + function reverseList(start, end) { + let [pre, cur] = [start, start.next] + const first = cur + while(cur !== end) { + let next = cur.next + cur.next = pre + pre = cur + cur = next + } + start.next = pre + first.next = cur + return first + } +}; + +``` + +## 参考(References) +- [Leetcode Discussion (yellowstone)](https://leetcode.com/problems/reverse-nodes-in-k-group/discuss/11440/Non-recursive-Java-solution-and-idea) + +## 扩展 + +- 要求从后往前以`k`个为一组进行翻转。**(字节跳动(ByteDance)面试题)** + + 例子,`1->2->3->4->5->6->7->8, k = 3`, + + 从后往前以`k=3`为一组, + - `6->7->8` 为一组翻转为`8->7->6`, + - `3->4->5`为一组翻转为`5->4->3`. + - `1->2`只有2个nodes少于`k=3`个,不翻转。 + + 最后返回: `1->2->5->4->3->8->7->6` + +这里的思路跟从前往后以`k`个为一组进行翻转类似,可以进行预处理: + +1. 翻转链表 + +2. 对翻转后的链表进行从前往后以k为一组翻转。 + +3. 翻转步骤2中得到的链表。 + +例子:`1->2->3->4->5->6->7->8, k = 3` + +1. 翻转链表得到:`8->7->6->5->4->3->2->1` + +2. 以k为一组翻转: `6->7->8->3->4->5->2->1` + +3. 翻转步骤#2链表: `1->2->5->4->3->8->7->6` + +## 类似题目 +- [Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/) \ No newline at end of file diff --git a/problems/25.reverse-nodes-in-k-groups-en.md b/problems/25.reverse-nodes-in-k-groups-en.md new file mode 100644 index 0000000..9cf1d31 --- /dev/null +++ b/problems/25.reverse-nodes-in-k-groups-en.md @@ -0,0 +1,214 @@ +## Problem +https://leetcode.com/problems/reverse-nodes-in-k-group/ + +## Problem Description +``` +Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. + +k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is. + +Example: + +Given this linked list: 1->2->3->4->5 + +For k = 2, you should return: 2->1->4->3->5 + +For k = 3, you should return: 3->2->1->4->5 + +Note: + +Only constant extra memory is allowed. +You may not alter the values in the list's nodes, only nodes itself may be changed. + +``` + +## Solution +Traverse `linked list` from left to right, during traverse, group nodes in k, then reverse each group. +How to reverse a linked list given start, end node? + +Reverse linked list: + +1. Initial a prev node `null` + +2. For each move, use temp node to keep current next node. + +3. During traverse, update current node pointing to previous node, update previous pointing to current node + +4. Update current to temp + +``` +ListNode temp = curr.next; +curr.next = prev; +prev = curr; +curr = temp; +``` + +For example(as below pic): reverse the whole linked list `1->2->3->4->null` -> `4->3->2->1->null` + +![reverse linked list](../assets/problems/25.reverse-nodes-in-k-groups-1.PNG) + +Here Reverse each group(`k nodes`): + +1. First group, use `count` keep track linked list counts when traverse linked list + +2. Use `start` to keep track each group start node position. + +3. Use `end ` to keep track each group end node position + +4. Reverse(`k nodes`)AKA: `(start, end) - start and end exclusively`. + +5. After reverse, update `start` point to reversed group last node. + +6. If `counts % k != 0`, then `end` move to next(`end=end.next`), for each move`count+1`. + +As below pic show steps 4 and 5, reverse linked list in range `(start, end)`: + +![reverse linked list range in (start, end)](../assets/problems/25.reverse-nodes-in-k-groups-3.png) + + +For example(as below pic),`head=[1,2,3,4,5,6,7,8], k = 3` + + +![reverse k nodes in linked list](../assets/problems/25.reverse-nodes-in-k-groups-2.PNG) + + +>**NOTE**: Usually we create a `dummy node` to solve linked list problem, because head node may be changed during operation. +for example: here `head updated from 1->3`, and `dummy (List(0)) ` keep the same. + +#### Complexity Analysis +- *Time Complexity:* `O(n) - n is number of Linked List` +- *Space Complexity:* `O(1)` + +## Key Points +1. create a dummy node, `dummy = ListNode(0)` +2. Group linked list as `k=3`, keep track of start and end node for each group. +3. Reverse each group, update start and end node references +4. return `dummy.next`. + +## Code (`Java/Python3`) +*Java Code* +```java +class ReverseKGroupsLinkedList { + public ListNode reverseKGroup(ListNode head, int k) { + if (head == null || k == 1) { + return head; + } + ListNode dummy = new ListNode(0); + dummy.next = head; + + ListNode start = dummy; + ListNode end = head; + int count = 0; + while (end != null) { + count++; + // group + if (count % k == 0) { + // reverse linked list (start, end] + start = reverse(start, end.next); + end = start.next; + } else { + end = end.next; + } + } + return dummy.next; + } + + /** + * reverse linked list from range (start, end), return last node. + * for example: + * 0->1->2->3->4->5->6->7->8 + * | | + * start end + * + * After call start = reverse(start, end) + * + * 0->3->2->1->4->5->6->7->8 + * | | + * start end + * + * @return the reversed list's 'start' node, which is the precedence of node end + */ + private ListNode reverse(ListNode start, ListNode end) { + ListNode curr = start.next; + ListNode prev = start; + ListNode first = curr; + while (curr != end){ + ListNode temp = curr.next; + curr.next = prev; + prev = curr; + curr = temp; + } + start.next = prev; + first.next = curr; + return first; + } +} +``` + +*Python3 Cose* +```python +class Solution: + def reverseKGroup(self, head: ListNode, k: int) -> ListNode: + if head is None or k < 2: + return head + dummy = ListNode(0) + dummy.next = head + start = dummy + end = head + count = 0 + while end: + count += 1 + if count % k == 0: + start = self.reverse(start, end.next) + end = start.next + else: + end = end.next + return dummy.next + + def reverse(self, start, end): + prev, curr = start, start.next + first = curr + while curr != end: + temp = curr.next + curr.next = prev + prev = curr + curr = temp + start.next = prev + first.next = curr + return first +``` + +## References +- [Leetcode Discussion (yellowstone)](https://leetcode.com/problems/reverse-nodes-in-k-group/discuss/11440/Non-recursive-Java-solution-and-idea) + +## Extension + +- Require from right to left reverse nodes in k groups. **(ByteDance Interview)** + + Example,`1->2->3->4->5->6->7->8, k = 3`, + + From right to left, group as `k=3`: + - `6->7->8` reverse to `8->7->6`, + - `3->4->5` reverse to `5->4->3`. + - `1->2` only has 2 nodes, which less than `k=3`, do nothing. + + return: `1->2->5->4->3->8->7->6` + +Here, we pre-process linked list, reverse it first, then using Reverse nodes in K groups solution: + +1. Reverse linked list + +2. From left to right, reverse linked list group as k nodes. + +3. Reverse step #2 linked list + +For example:`1->2->3->4->5->6->7->8, k = 3` + +1. Reverse linked list: `8->7->6->5->4->3->2->1` + +2. Reverse nodes in k groups: `6->7->8->3->4->5->2->1` + +3. Reverse step#2 linked list: `1->2->5->4->3->8->7->6` + +## Similar Problems +- [Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/) \ No newline at end of file diff --git a/problems/26.remove-duplicates-from-sorted-array.md b/problems/26.remove-duplicates-from-sorted-array.md new file mode 100644 index 0000000..eedbdb1 --- /dev/null +++ b/problems/26.remove-duplicates-from-sorted-array.md @@ -0,0 +1,127 @@ +## 题目地址 +https://leetcode.com/problems/remove-duplicates-from-sorted-array/description/ + +## 题目描述 +Given a sorted array nums, remove the duplicates in-place such that each element appear only once and return the new length. + +Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory. + +Example 1: + +Given nums = [1,1,2], + +Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively. + +It doesn't matter what you leave beyond the returned length. +Example 2: + +Given nums = [0,0,1,1,1,2,2,3,3,4], + +Your function should return length = 5, with the first five elements of nums being modified to 0, 1, 2, 3, and 4 respectively. + +It doesn't matter what values are set beyond the returned length. +Clarification: + +Confused why the returned value is an integer but your answer is an array? + +Note that the input array is passed in by reference, which means modification to the input array will be known to the caller as well. + +Internally you can think of this: + +``` +// nums is passed in by reference. (i.e., without making a copy) +int len = removeDuplicates(nums); + +// any modification to nums in your function would be known by the caller. +// using the length returned by your function, it prints the first len elements. +for (int i = 0; i < len; i++) { + print(nums[i]); +} +``` + +## 思路 + +使用快慢指针来记录遍历的坐标。 + +- 开始时这两个指针都指向第一个数字 + +- 如果两个指针指的数字相同,则快指针向前走一步 + +- 如果不同,则两个指针都向前走一步 + +- 当快指针走完整个数组后,慢指针当前的坐标加 1 就是数组中不同数字的个数 + +![26.remove-duplicates-from-sorted-array](../assets/26.remove-duplicates-from-sorted-array.gif) + +(图片来自: https://github.com/MisterBooo/LeetCodeAnimation) + +## 关键点解析 + +- 双指针 + +这道题如果不要求,O(n) 的时间复杂度, O(1) 的空间复杂度的话,会很简单。 +但是这道题是要求的,这种题的思路一般都是采用双指针 + +- 如果是数据是无序的,就不可以用这种方式了,从这里也可以看出排序在算法中的基础性和重要性。 + +- 注意nums为空时的边界条件。 + +## 代码 + +* 语言支持:JS,Python,C++ + +Javascript Code: +```js +/** + * @param {number[]} nums + * @return {number} + */ +var removeDuplicates = function(nums) { + const size = nums.length; + if(size==0) return 0; + let slowP = 0; + for (let fastP = 0; fastP < size; fastP++) { + if (nums[fastP] !== nums[slowP]) { + slowP++; + nums[slowP] = nums[fastP] + } + } + return slowP + 1; +}; +``` + +Python Code: +```python +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: + if nums: + slow = 0 + for fast in range(1, len(nums)): + if nums[fast] != nums[slow]: + slow += 1 + nums[slow] = nums[fast] + return slow + 1 + else: + return 0 +``` + +C++ Code: +```cpp +class Solution { +public: + int removeDuplicates(vector& nums) { + if(nums.empty()) return 0; + int fast,slow; + fast=slow=0; + while(fast!=nums.size()){ + if(nums[fast]==nums[slow]) fast++; + else { + slow++; + nums[slow]=nums[fast]; + fast++; + } + } + return slow+1; + } +}; +``` diff --git a/problems/263.ugly-number.md b/problems/263.ugly-number.md new file mode 100644 index 0000000..d8e04d5 --- /dev/null +++ b/problems/263.ugly-number.md @@ -0,0 +1,109 @@ +## 题目地址 + +https://leetcode.com/problems/ugly-number/description/ + +## 题目描述 + +``` +Write a program to check whether a given number is an ugly number. + +Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. + +Example 1: + +Input: 6 +Output: true +Explanation: 6 = 2 × 3 +Example 2: + +Input: 8 +Output: true +Explanation: 8 = 2 × 2 × 2 +Example 3: + +Input: 14 +Output: false +Explanation: 14 is not ugly since it includes another prime factor 7. +Note: + +1 is typically treated as an ugly number. +Input is within the 32-bit signed integer range: [−231, 231 − 1]. + +``` + +## 思路 + +题目要求给定一个数字,判断是否为“丑陋数”(ugly number), 丑陋数是指只包含质因子2, 3, 5的正整数。 + +![263.ugly-number](../assets/problems/263.ugly-number.png) + +根据定义,我们将给定数字除以2、3、5(顺序无所谓),直到无法整除。 +如果得到1,说明是所有因子都是2或3或5,如果不是1,则不是丑陋数。 + +这就好像我们判断一个数字是否为n(n为大于1的正整数)的幂次方一样,我们只需要 +不断除以n,直到无法整除,如果得到1,那么就是n的幂次方。 这道题的不同在于 +它不再是某一个数字的幂次方,而是三个数字(2,3,5),不过解题思路还是一样的。 + +转化为代码可以是: + +```js + + while(num % 2 === 0) num = num / 2; + while(num % 3 === 0) num = num / 3; + while(num % 5 === 0) num = num / 5; + + return num === 1; + +``` + +> 我下方给出的代码是用了递归实现,只是给大家看下不同的写法而已。 + +## 关键点 +- 数论 +- 因数分解 + +## 代码 + +* 语言支持:JS, Python + +Javascript Code: + +```js +/* + * @lc app=leetcode id=263 lang=javascript + * + * [263] Ugly Number + */ +/** + * @param {number} num + * @return {boolean} + */ +var isUgly = function(num) { + // TAG: 数论 + if (num <= 0) return false; + if (num === 1) return true; + + const list = [2, 3, 5]; + + if (list.includes(num)) return true; + + for (let i of list) { + if (num % i === 0) return isUgly(Math.floor(num / i)); + } + return false; +}; +``` + +Python Code: + +```python +# 非递归写法 +class Solution: + def isUgly(self, num: int) -> bool: + if num <= 0: + return False + for i in (2, 3, 5): + while num % i == 0: + num /= i + return num == 1 +``` diff --git a/problems/279.perfect-squares.md b/problems/279.perfect-squares.md new file mode 100644 index 0000000..174e364 --- /dev/null +++ b/problems/279.perfect-squares.md @@ -0,0 +1,140 @@ +## 题目地址 + +https://leetcode.com/problems/perfect-squares/description/ + +## 题目描述 + +``` +Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n. + +Example 1: + +Input: n = 12 +Output: 3 +Explanation: 12 = 4 + 4 + 4. +Example 2: + +Input: n = 13 +Output: 2 +Explanation: 13 = 4 + 9. + +``` + +## 思路 + +直接递归处理即可,但是这种暴力的解法很容易超时。如果你把递归的过程化成一棵树的话(其实就是递归树), +可以看出中间有很多重复的计算。 + +如果能将重复的计算缓存下来,说不定能够解决时间复杂度太高的问题。 + +> 递归对内存的要求也很高, 如果数字非常大,也会面临爆栈的风险,将递归转化为循环可以解决。 + +递归 + 缓存的方式代码如下: + +```js +const mapper = {}; + +function d(n, level) { + if (n === 0) return level; + + let i = 1; + const arr = []; + + while (n - i * i >= 0) { + const hit = mapper[n - i * i]; + if (hit) { + arr.push(hit + level); + } else { + const depth = d(n - i * i, level + 1) - level; + mapper[n - i * i] = depth; + arr.push(depth + level); + } + i++; + } + + return Math.min(...arr); +} +/** + * @param {number} n + * @return {number} + */ +var numSquares = function(n) { + return d(n, 0); +}; +``` + +如果使用 DP,其实本质上和递归 + 缓存 差不多。 + +DP 的代码见代码区。 + +## 关键点解析 + +- 如果用递归 + 缓存, 缓存的设计很重要 + 我的做法是 key 就是 n,value 是以 n 为起点,到达底端的深度。 + 下次取出缓存的时候用当前的 level + 存的深度 就是我们想要的 level. + +- 使用动态规划的核心点还是选和不选的问题 + +```js +for (let i = 1; i <= n; i++) { + for (let j = 1; j * j <= i; j++) { + // 不选(dp[i]) 还是 选(dp[i - j * j]) + dp[i] = Math.min(dp[i], dp[i - j * j] + 1); + } +} +``` + +## 代码 + +```js +/* + * @lc app=leetcode id=279 lang=javascript + * + * [279] Perfect Squares + * + * https://leetcode.com/problems/perfect-squares/description/ + * + * algorithms + * Medium (40.98%) + * Total Accepted: 168.2K + * Total Submissions: 408.5K + * Testcase Example: '12' + * + * Given a positive integer n, find the least number of perfect square numbers + * (for example, 1, 4, 9, 16, ...) which sum to n. + * + * Example 1: + * + * + * Input: n = 12 + * Output: 3 + * Explanation: 12 = 4 + 4 + 4. + * + * Example 2: + * + * + * Input: n = 13 + * Output: 2 + * Explanation: 13 = 4 + 9. + */ +/** + * @param {number} n + * @return {number} + */ +var numSquares = function(n) { + if (n <= 0) { + return 0; + } + + const dp = Array(n + 1).fill(Number.MAX_VALUE); + dp[0] = 0; + for (let i = 1; i <= n; i++) { + for (let j = 1; j * j <= i; j++) { + // 不选(dp[i]) 还是 选(dp[i - j * j]) + dp[i] = Math.min(dp[i], dp[i - j * j] + 1); + } + } + + return dp[n]; +}; +``` diff --git a/problems/283.move-zeroes.md b/problems/283.move-zeroes.md new file mode 100644 index 0000000..f4eb15e --- /dev/null +++ b/problems/283.move-zeroes.md @@ -0,0 +1,99 @@ + +## 题目地址 +https://leetcode.com/problems/move-zeroes/description/ + +## 题目描述 +``` +Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements. + +Example: + +Input: [0,1,0,3,12] +Output: [1,3,12,0,0] +Note: + +You must do this in-place without making a copy of the array. +Minimize the total number of operations. + +``` +## 思路 + +如果题目没有要求 modify in-place 的话,我们可以先遍历一遍将包含 0 的和不包含 0 的存到两个数组, +然后拼接两个数组即可。 但是题目要求 modify in-place, 也就是不需要借助额外的存储空间,刚才的方法 +空间复杂度是 O(n). + +那么如果 modify in-place 呢? 空间复杂度降低为 1。 + +我们可以借助一个游标记录位置,然后遍历一次,将非 0 的原地修改,最后补 0 即可。 + +## 关键点解析 + +无 + +## 代码 + +* 语言支持:JS, C++, Python + +JavaScript Code: + +```js +/** + * @param {number[]} nums + * @return {void} Do not return anything, modify nums in-place instead. + */ +var moveZeroes = function(nums) { + let index = 0; + for(let i = 0; i < nums.length; i++) { + const num = nums[i]; + if (num !== 0) { + nums[index++] = num; + } + } + + for(let i = index; i < nums.length; i++) { + nums[index++] = 0; + } +}; +``` + +C++ Code: + +> 解题思想与上面 JavaScript 一致,做了少许代码优化(非性能优化,因为时间复杂度都是 O(n)): +> 增加一个游标来记录下一个待处理的元素的位置,这样只需要写一次循环即可。 + +```C++ +class Solution { +public: + void moveZeroes(vector& nums) { + vector::size_type nonZero = 0; + vector::size_type next = 0; + while (next < nums.size()) { + if (nums[next] != 0) { + // 使用 std::swap() 会带来 8ms 的性能损失 + // swap(nums[next], nums[nonZero]); + auto tmp = nums[next]; + nums[next] = nums[nonZero]; + nums[nonZero] = tmp; + ++nonZero; + } + ++next; + } + } +}; +``` + +Python Code: + +```python +class Solution: + def moveZeroes(self, nums: List[int]) -> None: + """ + Do not return anything, modify nums in-place instead. + """ + slow = fast = 0 + while fast < len(nums): + if nums[fast] != 0: + nums[fast], nums[slow] = nums[slow], nums[fast] + slow += 1 + fast += 1 +``` diff --git a/problems/29.divide-two-integers.md b/problems/29.divide-two-integers.md new file mode 100644 index 0000000..b324dad --- /dev/null +++ b/problems/29.divide-two-integers.md @@ -0,0 +1,172 @@ +## 题目地址 +https://leetcode.com/problems/divide-two-integers/description/ + +## 题目描述 +``` +Given two integers dividend and divisor, divide two integers without using multiplication, division and mod operator. + +Return the quotient after dividing dividend by divisor. + +The integer division should truncate toward zero. + +Example 1: + +Input: dividend = 10, divisor = 3 +Output: 3 +Example 2: + +Input: dividend = 7, divisor = -3 +Output: -2 +Note: + +Both dividend and divisor will be 32-bit signed integers. +The divisor will never be 0. +Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−231, 231 − 1]. For the purpose of this problem, assume that your function returns 231 − 1 when the division result overflows. + +``` + +## 思路 + +符合直觉的做法是,减数一次一次减去被减数,不断更新差,直到差小于0,我们减了多少次,结果就是多少。 + +核心代码: + +```js + let acc = divisor; + let count = 0; + + while (dividend - acc >= 0) { + acc += divisor; + count++; + } + + return count; + +``` + +这种做法简单直观,但是性能却比较差. 下面来介绍一种性能更好的方法。 + +![29.divide-two-integers](../assets/problems/29.divide-two-integers.png) + +通过上面这样的分析,我们直到可以使用二分法来解决,性能有很大的提升。 + +## 关键点解析 + +- 二分查找 + +- 正负数的判断中,这样判断更简单。 + +```js +const isNegative = dividend > 0 !== divisor > 0; +``` + + +## 代码 + +* 语言支持:JS,Python3 + +```js + + +/* + * @lc app=leetcode id=29 lang=javascript + * + * [29] Divide Two Integers + */ +/** + * @param {number} dividend + * @param {number} divisor + * @return {number} + */ +var divide = function(dividend, divisor) { + if (divisor === 1) return dividend; + + // 这种方法很巧妙,即符号相同则为正,不同则为负 + const isNegative = dividend > 0 !== divisor > 0; + + const MAX_INTERGER = Math.pow(2, 31); + + const res = helper(Math.abs(dividend), Math.abs(divisor)); + + // overflow + if (res > MAX_INTERGER - 1 || res < -1 * MAX_INTERGER) { + return MAX_INTERGER - 1; + } + + return isNegative ? -1 * res : res; +}; + +function helper(dividend, divisor) { + // 二分法 + if (dividend <= 0) return 0; + if (dividend < divisor) return 0; + if (divisor === 1) return dividend; + + let acc = 2 * divisor; + let count = 1; + + while (dividend - acc > 0) { + acc += acc; + count += count; + } + // 直接使用位移运算,比如acc >> 1会有问题 + const last = dividend - Math.floor(acc / 2); + + return count + helper(last, divisor); +} +``` +Python3 Code: +```python +class Solution: + def divide(self, dividend: int, divisor: int) -> int: + """ + 二分法 + :param int divisor + :param int dividend + :return int + """ + # 错误处理 + if divisor == 0: + raise ZeroDivisionError + if abs(divisor) == 1: + result = dividend if 1 == divisor else -dividend + return min(2**31-1, max(-2**31, result)) + + # 确定结果的符号 + sign = ((dividend >= 0) == (divisor >= 0)) + + result = 0 + # abs也可以直接写在while条件中,不过可能会多计算几次 + _divisor = abs(divisor) + _dividend = abs(dividend) + + while _divisor <= _dividend: + r, _dividend = self._multi_divide(_divisor, _dividend) + result += r + + result = result if sign else -result + + # 注意返回值不能超过32位有符号数的表示范围 + return min(2**31-1, max(-2**31, result)) + + def _multi_divide(self, divisor, dividend): + """ + 翻倍除法,如果可以被除,则下一步除数翻倍,直至除数大于被除数, + 返回商加总的结果与被除数的剩余值; + 这里就不做异常处理了; + :param int divisor + :param int dividend + :return tuple result, left_dividend + """ + result = 0 + times_count = 1 + while divisor <= dividend: + dividend -= divisor + result += times_count + times_count += times_count + divisor += divisor + return result, dividend +``` + +## 相关题目 +- [875.koko-eating-bananas](./875.koko-eating-bananas.md) diff --git a/problems/295.find-median-from-data-stream.md b/problems/295.find-median-from-data-stream.md new file mode 100644 index 0000000..2ac1b36 --- /dev/null +++ b/problems/295.find-median-from-data-stream.md @@ -0,0 +1,325 @@ +## 题目地址 + +https://leetcode.com/problems/find-median-from-data-stream/description/ + +## 题目描述 + +``` +Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value. + +For example, +[2,3,4], the median is 3 + +[2,3], the median is (2 + 3) / 2 = 2.5 + +Design a data structure that supports the following two operations: + +void addNum(int num) - Add a integer number from the data stream to the data structure. +double findMedian() - Return the median of all elements so far. + + +Example: + +addNum(1) +addNum(2) +findMedian() -> 1.5 +addNum(3) +findMedian() -> 2 + + +Follow up: + +If all integer numbers from the stream are between 0 and 100, how would you optimize it? +If 99% of all integer numbers from the stream are between 0 and 100, how would you optimize it? + +``` + +## 思路 + +这道题目是求动态数据的中位数,在 leetcode 难度为`hard`. 如果这道题是求静态数据的中位数,我们用数组去存储, +空间复杂度 O(1), 时间复杂度 O(1) + +> 空间复杂度指的是除了存储数据之外额外开辟的用于计算等任务的内存空间 + +代码也比较简单 + +```js +function findMedian(a) { + return a.length % 2 === 0 + ? (a[a.length >> 1] + a[a.length >> (1 + 1)]) / 2 + : a[a.length >> 1]; +} +``` + +但是题目要求是动态数据, 那么是否可以每次添加数据的时候,都去排一次序呢? +假如我们每次插入都用`快速排序`进行排序的话,那么时间复杂度是 O(nlogn) + O(1) + +> O(nlogn) 是排序的时间复杂度 O(1)是查询中位数的时间复杂度 + +如果你用这种思路进行的话, 恐怕 leetcode 会超时。 + +那么如何优化呢? 答案是使用堆, Java, C++等语言都有`优先级队列`中这种数据结构, +优先级队列本质上就是一个堆。 关于堆和优先级队列的关系,我会放在《数据结构和算法》部分讲解。这里不赘述 + +如果借助堆这种数据结构, 就可以轻易实现了。 + +具体的做法是,建立两个堆,这两个堆需要满足: + +1. 大顶堆元素都比小顶堆小(由于堆的特点其实只要比较堆顶即可) +2. 大顶堆元素不小于小顶堆,且最多比小顶堆多一个元素 + +满足上面两个条件的话,如果想要找到中位数,就比较简单了 + +- 如果两个堆数量相等(本质是总数为偶数), 就两个堆顶元素的平均数 +- 如果两个堆数量不相等(本质是总数为奇数), 就取大顶堆的堆顶元素 + +比如对于[1,2,3] 求中位数: + +![295.find-median-from-data-stream-1](../assets/problems/295.find-median-from-data-stream-1.png) + +再比如对于[1,2,3, 4] 求中位数: + +![295.find-median-from-data-stream-2](../assets/problems/295.find-median-from-data-stream-2.png) +## 关键点解析 + +- 用两个堆(一个大顶堆,一个小顶堆)来简化时间复杂度 +- 用优先级队列简化操作 + +> JavaScript 不像 Java, C++等语言都有`优先级队列`中这种数据结构, 因此大家可以使用社区的实现 +> 个人认为没有非要纠结于优先级队列怎么实现, 至少这道题不是考这个的 +> 优先级队列的实现个人认为已经超过了这道题想考察的范畴 +## 代码 + +如果不使用现成的`优先级队列`这种数据结构,代码可能是这样的: + +```js +/** + * initialize your data structure here. + */ +var MedianFinder = function() { + this.maxHeap = []; + this.minHeap = []; +}; + +function minHeapify() { + this.minHeap.unshift(null); + const a = this.minHeap; + + // 为了方便大家理解,这里选用了粗暴的实现 + // 时间复杂度为O(n) + // 其实可以降到O(logn), 具体细节我不想在这里讲解和实现 + for (let i = a.length - 1; i >> 1 > 0; i--) { + // 自下往上堆化 + if (a[i] < a[i >> 1]) { // 如果子元素更小,则交换位置 + const temp = a[i]; + this.minHeap[i] = a[i >> 1]; + this.minHeap[i >> 1] = temp; + } + } + this.minHeap.shift(null); +} + +function maxHeapify() { + this.maxHeap.unshift(null); + const a = this.maxHeap; + + // 为了方便大家理解,这里选用了粗暴的实现 + // 时间复杂度为O(n) + // 其实可以降到O(logn), 具体细节我不想在这里讲解和实现 + for (let i = a.length - 1; i >> 1 > 0; i--) { + // 自下往上堆化 + if (a[i] > a[i >> 1]) { // 如果子元素更大,则交换位置 + const temp = a[i]; + this.maxHeap[i] = a[i >> 1]; + this.maxHeap[i >> 1] = temp; + } + } + this.maxHeap.shift(null); +} + +/** + * @param {number} num + * @return {void} + */ +MedianFinder.prototype.addNum = function(num) { + // 为了大家容易理解,这部分代码写的比较冗余 + + // 插入 + if (num >= (this.minHeap[0] || Number.MIN_VALUE)) { + this.minHeap.push(num); + } else { + this.maxHeap.push(num); + } + // 调整两个堆的节点数量平衡 + // 使得大顶堆的数量最多大于小顶堆一个, 且一定不小于小顶堆数量 + if (this.maxHeap.length > this.minHeap.length + 1) { + // 大顶堆的堆顶元素移动到小顶堆 + this.minHeap.push(this.maxHeap.shift()); + } + + if (this.minHeap.length > this.maxHeap.length) { + // 小顶堆的堆顶元素移动到大顶堆 + this.maxHeap.push(this.minHeap.shift()); + } + + // 调整堆顶元素 + if (this.maxHeap[0] > this.minHeap[0]) { + const temp = this.maxHeap[0]; + this.maxHeap[0] = this.minHeap[0]; + this.minHeap[0] = temp; + } + + // 堆化 + maxHeapify.call(this); + minHeapify.call(this); +}; + +/** + * @return {number} + */ +MedianFinder.prototype.findMedian = function() { + if ((this.maxHeap.length + this.minHeap.length) % 2 === 0) { + return (this.minHeap[0] + this.maxHeap[0]) / 2; + } else { + return this.maxHeap[0]; + } +}; + +/** + * Your MedianFinder object will be instantiated and called as such: + * var obj = new MedianFinder() + * obj.addNum(num) + * var param_2 = obj.findMedian() + */ +``` + +其中`minHeapify` 和 `maxHeapify` 的过程都有一个hack操作,就是: + +```js + +this.heap.unshift(null); +// .... +this.heap.shift(null); + +``` + +其实就是为了存储的数据从1开始,这样方便计算。 即对于下标为i的元素, `i >> 1` 一定是父节点的下标。 + +![295.find-median-from-data-stream-3](../assets/problems/295.find-median-from-data-stream-3.png) + +> 这是因为我用满二叉树来存储的堆 + +这个实现比较繁琐,下面介绍一种优雅的方式,假设JS和Java和C++等语言一样有`PriorityQueue`这种数据结构,那么我们实现就比较简单了。 + +代码: + +> 关于PriorityQueue的实现,感兴趣的可以看下 https://github.com/janogonzalez/priorityqueuejs + +```js +/* + * @lc app=leetcode id=295 lang=javascript + * + * [295] Find Median from Data Stream + * + * https://leetcode.com/problems/find-median-from-data-stream/description/ + * + * algorithms + * Hard (35.08%) + * Total Accepted: 101.2K + * Total Submissions: 282.4K + * Testcase Example: '["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]\n[[],[1],[2],[],[3],[]]' + * + * Median is the middle value in an ordered integer list. If the size of the + * list is even, there is no middle value. So the median is the mean of the two + * middle value. + * For example, + * + * [2,3,4], the median is 3 + * + * [2,3], the median is (2 + 3) / 2 = 2.5 + * + * Design a data structure that supports the following two operations: + * + * + * void addNum(int num) - Add a integer number from the data stream to the data + * structure. + * double findMedian() - Return the median of all elements so far. + * + * + * + * + * Example: + * + * + * addNum(1) + * addNum(2) + * findMedian() -> 1.5 + * addNum(3) + * findMedian() -> 2 + * + * + * + * + * Follow up: + * + * + * If all integer numbers from the stream are between 0 and 100, how would you + * optimize it? + * If 99% of all integer numbers from the stream are between 0 and 100, how + * would you optimize it? + * + * + */ +/** + * initialize your data structure here. + */ +var MedianFinder = function() { + this.maxHeap = new PriorityQueue((a, b) => a - b); + this.minHeap = new PriorityQueue((a, b) => b - a); +}; + +/** + * @param {number} num + * @return {void} + */ +MedianFinder.prototype.addNum = function(num) { + // 我们的目标就是建立两个堆,一个大顶堆,一个小顶堆 + // 结合中位数的特点 + // 这两个堆需要满足: + // 1. 大顶堆元素都比小顶堆小(由于堆的特点其实只要比较堆顶即可) + // 2. 大顶堆元素不小于小顶堆,且最多比小顶堆多一个元素 + + // 满足上面两个条件的话,如果想要找到中位数,就比较简单了 + // 如果两个堆数量相等(本质是总数为偶数), 就两个堆顶元素的平均数 + // 如果两个堆数量不相等(本质是总数为奇数), 就取大顶堆的堆顶元素 + + // 问题如果保证满足上述两个特点 + + // 1. 保证第一点 + this.maxHeap.enq(num); + // 由于小顶堆的所有数都来自大顶堆的堆顶元素(最大值) + // 因此可以保证第一点 + this.minHeap.enq(this.maxHeap.deq()); + + // 2. 保证第二点 + if (this.maxHeap.size() < this.minHeap.size()){ + this.maxHeap.enq(this.minHeap.deq()); + } +}; + +/** + * @return {number} + */ +MedianFinder.prototype.findMedian = function() { + if (this.maxHeap.size() == this.minHeap.size()) return (this.maxHeap.peek() + this.minHeap.peek()) / 2.0; + else return this.maxHeap.peek(); +}; + +/** + * Your MedianFinder object will be instantiated and called as such: + * var obj = new MedianFinder() + * obj.addNum(num) + * var param_2 = obj.findMedian() + */ + +``` diff --git a/problems/3.longestSubstringWithoutRepeatingCharacters.md b/problems/3.longestSubstringWithoutRepeatingCharacters.md new file mode 100644 index 0000000..fd54533 --- /dev/null +++ b/problems/3.longestSubstringWithoutRepeatingCharacters.md @@ -0,0 +1,102 @@ +## 题目地址 + +https://leetcode.com/problems/longest-substring-without-repeating-characters/description/ + +## 题目描述 + +Given a string, find the length of the longest substring without repeating characters. + +Examples: + +``` +Given "abcabcbb", the answer is "abc", which the length is 3. + +Given "bbbbb", the answer is "b", with the length of 1. + +Given "pwwkew", the answer is "wke", with the length of 3. Note that the answer must be a substring, "pwke" is a subsequence and not a substring. +``` + +## 思路 + +用一个 hashmap 来建立字符和其出现位置之间的映射。 + +维护一个滑动窗口,窗口内的都是没有重复的字符,去尽可能的扩大窗口的大小,窗口不停的向右滑动。 + +(1)如果当前遍历到的字符从未出现过,那么直接扩大右边界; + +(2)如果当前遍历到的字符出现过,则缩小窗口(左边索引向右移动),然后继续观察当前遍历到的字符; + +(3)重复(1)(2),直到左边索引无法再移动; + +(4)维护一个结果 res,每次用出现过的窗口大小来更新结果 res,最后返回 res 获取结果。 + +![3.longestSubstringWithoutRepeatingCharacters](../assets/3.longestSubstringWithoutRepeatingCharacters.gif) + +(图片来自: https://github.com/MisterBooo/LeetCodeAnimation) + +## 关键点 + +1. 用一个 mapper 记录出现过并且没有被删除的字符 +2. 用一个滑动窗口记录当前 index 开始的最大的不重复的字符序列 +3. 用 res 去记录目前位置最大的长度,每次滑动窗口更新就去决定是否需要更新 res + +## 代码 + +代码支持:JavaScript,Python3 + +JavaScript Code: + +```js +/** + * @param {string} s + * @return {number} + */ +var lengthOfLongestSubstring = function(s) { + const mapper = {}; // 记录已经出现过的charactor + let res = 0; + let slidingWindow = []; + + for (let c of s) { + if (mapper[c]) { + // 已经出现过了 + // 则删除 + const delIndex = slidingWindow.findIndex(_c => _c === c); + + for (let i = 0; i < delIndex; i++) { + mapper[slidingWindow[i]] = false; + } + + slidingWindow = slidingWindow.slice(delIndex + 1).concat(c); + } else { + // 新字符 + if (slidingWindow.push(c) > res) { + res = slidingWindow.length; + } + } + mapper[c] = true; + } + return res; +}; +``` + +Python3 Code: + +```python +from collections import defaultdict + + +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + l = 0 + ans = 0 + counter = defaultdict(lambda: 0) + + for r in range(len(s)): + while counter.get(s[r], 0) != 0: + counter[s[l]] = counter.get(s[l], 0) - 1 + l += 1 + counter[s[r]] += 1 + ans = max(ans, r - l + 1) + + return ans +``` diff --git a/problems/30.substring-with-concatenation-of-all-words.md b/problems/30.substring-with-concatenation-of-all-words.md new file mode 100644 index 0000000..ce9999e --- /dev/null +++ b/problems/30.substring-with-concatenation-of-all-words.md @@ -0,0 +1,75 @@ +## 题目地址(30. 串联所有单词的子串) + +https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/description/ + +## 题目描述 + +``` +给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。 + +注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。 + +  + +示例 1: + +输入: + s = "barfoothefoobarman", + words = ["foo","bar"] +输出:[0,9] +解释: +从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。 +输出的顺序不重要, [9,0] 也是有效答案。 +示例 2: + +输入: + s = "wordgoodgoodgoodbestword", + words = ["word","good","best","word"] +输出:[] + + +``` + +## 思路 + +本题是要我们找出 words 中`所有单词按照任意顺序串联`形成的单词中恰好出现在 s 中的索引,因此顺序是不重要的。换句话说,我们只要统计每一个单词的出现情况即可。以题目中 s = "barfoothefoobarman", words = ["foo","bar"] 为例。 我们只需要统计 foo 出现了一次,bar 出现了一次即可。我们只需要在 s 中找到同样包含一次 foo 和一次 bar 的子串即可。由于 words 中的字符串都是等长的,因此编码上也会比较简单。 + +1. 我们的目标状态是 Counter(words),即对 words 进行一次计数。 +2. 我们只需从头开始遍历一次数组,每次截取 word 长度的字符,一共截取 words 长度次即可。 +3. 如果我们截取的 Counter 和 Counter(words)一致,则加入到 res +4. 否则我们继续一个指针,继续执行步骤二 +5. 重复执行这个逻辑直到达到数组尾部 + +## 关键点解析 + +- Counter + +## 代码 + +- 语言支持:Python3 + +```python +from collections import Counter + + +class Solution: + def findSubstring(self, s: str, words: List[str]) -> List[int]: + if not s or not words: + return [] + res = [] + n = len(words) + word_len = len(words[0]) + window_len = word_len * n + target = Counter(words) + i = 0 + while i < len(s) - window_len + 1: + sliced = [] + start = i + for _ in range(n): + sliced.append(s[start:start + word_len]) + start += word_len + if Counter(sliced) == target: + res.append(i) + i += 1 + return res +``` diff --git a/problems/301.remove-invalid-parentheses.md b/problems/301.remove-invalid-parentheses.md new file mode 100644 index 0000000..9e30850 --- /dev/null +++ b/problems/301.remove-invalid-parentheses.md @@ -0,0 +1,141 @@ + +## 题目地址 +https://leetcode.com/problems/remove-invalid-parentheses/description/ + +## 题目描述 +``` +Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results. + +Note: The input string may contain letters other than the parentheses ( and ). + +Example 1: + +Input: "()())()" +Output: ["()()()", "(())()"] +Example 2: + +Input: "(a)())()" +Output: ["(a)()()", "(a())()"] +Example 3: + +Input: ")(" +Output: [""] + +``` + +## 思路 + +我们的思路是先写一个函数用来判断给定字符串是否是有效的。 然后再写一个函数,这个函数 +依次删除第i个字符,判断是否有效,有效则添加进最终的返回数组。 + +这样的话实现的功能就是, 删除`一个` 小括号使之有效的所有可能。因此只需要递归调用`依次删除第i个字符`的功能就可以了。 + +而且由于题目要求是要删除最少的小括号,因此我们的思路是使用广度优先遍历,而不是深度有限的遍历。 + +![301.remove-invalid-parentheses](../assets/problems/301.remove-invalid-parentheses.png) + +> 没有动图,请脑补 + +## 关键点解析 + +- 广度优先遍历 + +- 使用队列简化操作 + +- 使用一个visited的mapper, 来避免遍历同样的字符串 + + +## 代码 +```js +/* + * @lc app=leetcode id=301 lang=javascript + * + * [301] Remove Invalid Parentheses + * + * https://leetcode.com/problems/remove-invalid-parentheses/description/ + * + * algorithms + * Hard (38.52%) + * Total Accepted: 114.3K + * Total Submissions: 295.4K + * Testcase Example: '"()())()"' + * + * Remove the minimum number of invalid parentheses in order to make the input + * string valid. Return all possible results. + * + * Note: The input string may contain letters other than the parentheses ( and + * ). + * + * Example 1: + * + * + * Input: "()())()" + * Output: ["()()()", "(())()"] + * + * + * Example 2: + * + * + * Input: "(a)())()" + * Output: ["(a)()()", "(a())()"] + * + * + * Example 3: + * + * + * Input: ")(" + * Output: [""] + * + */ +var isValid = function(s) { + let openParenthes = 0; + for(let i = 0; i < s.length; i++) { + if (s[i] === '(') { + openParenthes++; + } else if (s[i] === ')') { + if (openParenthes === 0) return false; + openParenthes--; + } + } + return openParenthes === 0; +}; +/** + * @param {string} s + * @return {string[]} + */ +var removeInvalidParentheses = function(s) { + if (!s || s.length === 0) return [""]; + const ret = []; + const queue = [s]; + const visited = {}; + let current = null; + let removedParentheses = 0; // 只记录最小改动 + + while ((current = queue.shift())) { + let hit = isValid(current); + if (hit) { + if (!removedParentheses) { + removedParentheses = s.length - current.length + } + if (s.length - current.length > removedParentheses) return ret.length === 0 ? [""] : ret;; + ret.unshift(current); + continue; + } + for (let i = 0; i < current.length; i++) { + if (current[i] !== ')' && current[i] !== '(') continue; + const subString = current.slice(0, i).concat(current.slice(i + 1)); + if (visited[subString]) continue; + visited[subString] = true; + queue.push(subString); + } + } + + return ret.length === 0 ? [""] : ret; +}; +``` + +## 扩展 + +相似问题: + +[validParentheses](./validParentheses.md) diff --git a/problems/309.best-time-to-buy-and-sell-stock-with-cooldown.md b/problems/309.best-time-to-buy-and-sell-stock-with-cooldown.md new file mode 100644 index 0000000..c2372d3 --- /dev/null +++ b/problems/309.best-time-to-buy-and-sell-stock-with-cooldown.md @@ -0,0 +1,106 @@ +## 题目地址 + +https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/ + +## 题目描述 + +``` +Say you have an array for which the ith element is the price of a given stock on day i. + +Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions: + +You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again). +After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day) +Example: + +Input: [1,2,3,0,2] +Output: 3 +Explanation: transactions = [buy, sell, cooldown, buy, sell] +``` + +## 思路 + +这是一道典型的 DP 问题, DP 问题的核心是找到状态和状态转移方程。 + +这道题目的状态似乎比我们常见的那种 DP 问题要多,这里的状态有 buy sell cooldown 三种, +我们可以用三个数组来表示这这三个状态,buy,sell, cooldown. + +- buy[i]表示第 i 天,且以 buy 结尾的最大利润 +- sell[i]表示第 i 天,且以 sell 结尾的最大利润 +- cooldown[i]表示第 i 天,且以 sell 结尾的最大利润 + +我们思考一下,其实 cooldown 这个状态数组似乎没有什么用,因此 cooldown 不会对`profit`产生 +任何影响。 我们可以进一步缩小为两种状态。 + +- buy[i] 表示第 i 天,且以 buy 或者 coolwown 结尾的最大利润 +- sell[i] 表示第 i 天,且以 sell 或者 cooldown 结尾的最大利润 + +对应的状态转移方程如下: + +> 这个需要花点时间来理解 + +``` + buy[i] = Math.max(buy[i - 1], sell[i - 2] - prices[i]); + sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]); +``` + +我们来分析一下,buy[i]对应第 i 的 action 只能是 buy 或者 cooldown。 + +- 如果是 cooldown,那么 profit 就是 buy[i - 1] +- 如果是 buy,那么就是`前一个卖的profit减去今天买股票花的钱`,即 sell[i -2] - prices[i] + +> 注意这里是 i - 2,不是 i-1 ,因为有 cooldown 的限制 + +sell[i]对应第 i 的 action 只能是 sell 或者 cooldown。 + +- 如果是 cooldown,那么 profit 就是 sell[i - 1] +- 如果是 sell,那么就是`前一次买的时候获取的利润加上这次卖的钱`,即 buy[i - 1] + prices[i] + +## 关键点解析 + +- 多状态动态规划 + +## 代码 + +```js +/* + * @lc app=leetcode id=309 lang=javascript + * + * [309] Best Time to Buy and Sell Stock with Cooldown + * + */ +/** + * @param {number[]} prices + * @return {number} + */ +var maxProfit = function(prices) { + if (prices == null || prices.length <= 1) return 0; + + // 定义状态变量 + const buy = []; + const sell = []; + // 寻常 + buy[0] = -prices[0]; + buy[1] = Math.max(-prices[0], -prices[1]); + sell[0] = 0; + sell[1] = Math.max(0, prices[1] - prices[0]); + for (let i = 2; i < prices.length; i++) { + // 状态转移方程 + // 第i天只能是买或者cooldown + // 如果买利润就是sell[i - 2] - prices[i], 注意这里是i - 2,不是 i-1 ,因为有cooldown的限制 + // cooldown就是buy[i -1] + buy[i] = Math.max(buy[i - 1], sell[i - 2] - prices[i]); + // 第i天只能是卖或者cooldown + // 如果卖利润就是buy[i -1] + prices[i] + // cooldown就是sell[i -1] + sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]); + } + + return Math.max(buy[prices.length - 1], sell[prices.length - 1], 0); +}; +``` + +## 相关题目 + +- [121.best-time-to-buy-and-sell-stock](./121.best-time-to-buy-and-sell-stock.md) +- [122.best-time-to-buy-and-sell-stock-ii](./122.best-time-to-buy-and-sell-stock-ii.md) diff --git a/problems/31.next-permutation.md b/problems/31.next-permutation.md new file mode 100644 index 0000000..29c8f00 --- /dev/null +++ b/problems/31.next-permutation.md @@ -0,0 +1,175 @@ +## 题目地址 + +https://leetcode.com/problems/next-permutation/description/ + +## 题目描述 + +``` +Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. + +If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order). + +The replacement must be in-place and use only constant extra memory. + +Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column. + +1,2,3 → 1,3,2 +3,2,1 → 1,2,3 +1,1,5 → 1,5,1 + +``` + +## 思路 + +符合直觉的方法是我们按顺序求出所有的排列,如果当前排列等于 nums,那么我直接取下一个 +但是这种做法不符合 constant space 要求(题目要求直接修改原数组),时间复杂度也太高,为 O(n!),肯定不是合适的解。 + +这种题目比较抽象,写几个例子通常会帮助理解问题的规律。我找了几个例子,其中蓝色背景表示的是当前数字找下一个更大排列的时候`需要改变的元素`. + +![31.next-permutation](../assets/problems/31.next-permutation-1.jpg) + +我们不难发现,蓝色的数字都是从后往前第一个不递增的元素,并且我们的下一个更大的排列 +只需要改变蓝色的以及之后部分即可,前面的不需要变。 + +那么怎么改变蓝色的以及后面部分呢?为了使增量最小, +由于前面我们观察发现,其实剩下的元素从左到右是递减的,而我们想要变成递增的,我们只需要不断交换首尾元素即可。 + + +另外我们也可以以回溯的角度来思考这个问题,让我们先回溯一次: + +![31.next-permutation-2](../assets/problems/31.next-permutation-2.jpg) + +这个时候可以选择的元素只有2,我们无法组成更大的排列,我们继续回溯,直到如图: + +![31.next-permutation-3](../assets/problems/31.next-permutation-3.jpg) + +我们发现我们可以交换4或者2实现变大的效果,但是要保证变大的幅度最小(下一个更大), +我们需要选择最小的,由于之前我们发现后面是从左到右递减的,显然就是交换最右面大于1的。 + +之后就是不断交换使之幅度最小: + +![31.next-permutation-4](../assets/problems/31.next-permutation-4.jpg) + +## 关键点解析 +- 写几个例子通常会帮助理解问题的规律 +- 在有序数组中首尾指针不断交换位置即可实现reverse +- 找到从右边起`第一个大于nums[i]的`,并将其和nums[i]进行交换 +## 代码 + +* 语言支持: Javascript,Python3 + +```js +/* + * @lc app=leetcode id=31 lang=javascript + * + * [31] Next Permutation + */ + +function reverseRange(A, i, j) { + while (i < j) { + const temp = A[i]; + A[i] = A[j]; + A[j] = temp; + i++; + j--; + } +} +/** + * @param {number[]} nums + * @return {void} Do not return anything, modify nums in-place instead. + */ +var nextPermutation = function(nums) { + // 时间复杂度O(n) 空间复杂度O(1) + if (nums == null || nums.length <= 1) return; + + let i = nums.length - 2; + // 从后往前找到第一个降序的,相当于找到了我们的回溯点 + while (i > -1 && nums[i + 1] <= nums[i]) i--; + + // 如果找了就swap + if (i > -1) { + let j = nums.length - 1; + // 找到从右边起第一个大于nums[i]的,并将其和nums[i]进行交换 + // 因为如果交换的数字比nums[i]还要小肯定不符合题意 + while (nums[j] <= nums[i]) j--; + const temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } + + // 最后我们只需要将剩下的元素从左到右,依次填入当前最小的元素就可以保证是大于当前排列的最小值了 + // [i + 1, A.length -1]的元素进行反转 + + reverseRange(nums, i + 1, nums.length - 1); +}; +``` +Python3 Code: +```python +class Solution: + def nextPermutation(self, nums): + """ + Do not return anything, modify nums in-place instead. + :param list nums + """ + # 第一步,从后往前,找到下降点 + down_index = None + for i in range(len(nums)-2, -1, -1): + if nums[i] < nums[i+1]: + down_index = i + break + # 如果没有下降点,重新排列 + if down_index is None: + nums.reverse() + # 如果有下降点 + else: + # 第二步,从后往前,找到比下降点大的数,对换位置 + for i in range(len(nums)-1, i, -1): + if nums[down_index] < nums[i]: + nums[down_index], nums[i] = nums[i], nums[down_index] + break + # 第三部,重新排列下降点之后的数 + i, j = down_index+1, len(nums)-1 + while i < j: + nums[i], nums[j] = nums[j], nums[i] + i += 1 + j -= 1 +``` + +Python3 Code: + +```python +class Solution: + def nextPermutation(self, nums): + """ + Do not return anything, modify nums in-place instead. + :param list nums + """ + # 第一步,从后往前,找到下降点 + down_index = None + for i in range(len(nums)-2, -1, -1): + if nums[i] < nums[i+1]: + down_index = i + break + # 如果没有下降点,重新排列 + if down_index is None: + nums.reverse() + # 如果有下降点 + else: + # 第二步,从后往前,找到比下降点大的数,对换位置 + for i in range(len(nums)-1, i, -1): + if nums[down_index] < nums[i]: + nums[down_index], nums[i] = nums[i], nums[down_index] + break + # 第三步,重新排列下降点之后的数 + i, j = down_index+1, len(nums)-1 + while i < j: + nums[i], nums[j] = nums[j], nums[i] + i += 1 + j -= 1 +``` + +## 相关题目 + +- [46.next-permutation](./46.next-permutation.md) +- [47.permutations-ii](./47.permutations-ii.md) +- [60.permutation-sequence](./60.permutation-sequence.md)(TODO) diff --git a/problems/32.longest-valid-parentheses.md b/problems/32.longest-valid-parentheses.md new file mode 100644 index 0000000..370d896 --- /dev/null +++ b/problems/32.longest-valid-parentheses.md @@ -0,0 +1,110 @@ +## 题目地址 +https://leetcode.com/problems/longest-valid-parentheses/ + +## 题目描述 + +``` +Given a string containing just the characters '(' and ')', find the length of the longest valid (well-formed) parentheses substring. + +Example 1: + +Input: "(()" +Output: 2 +Explanation: The longest valid parentheses substring is "()" +Example 2: + +Input: ")()())" +Output: 4 +Explanation: The longest valid parentheses substring is "()()" +``` + +## 思路(动态规划) + +所有的动态规划问题, 首先需要解决的就是如何寻找合适的子问题. +该题需要我们找到最长的有效括号对, 我们首先想到的就是定义**dp[i]为前i个字符串的最长有效括号对长度**, 但是随后我们会发现, 这样的定义, 我们无法找到dp[i]和dp[i-1]的任何关系. +所以, 我们需要重新找一个新的定义: 定义**dp[i]为以第i个字符结尾的最长有效括号对长度**. 然后, 我们通过下面这个例子找一下dp[i]和dp[i-1]之间的关系. + +```python +s = '(())())' +``` + +从上面的例子我们可以观察出一下几点结论(**描述中i为图中的dp数组的下标, 对应s的下标应为i-1, 第i个字符的i从1开始**). +1. base case: 空字符串的最长有效括号对长度肯定为0, 即: dp[0] = 0; +2. s的第**1**个字符结尾的最长有效括号对长度为0, s的第**2**个字符结尾的最长有效括号对长度也为0, 这个时候我们可以得出结论: 最长有效括号对不可能以'('结尾, 即: dp[1] = d[2] = 0; +3. 当i等于3时, 我们可以看出dp[2]=0, dp[3]=2, 因为第2个字符(**s[1]**)和第3个字符(**s[2]**)是配对的; + 当i等于4时, 我们可以看出dp[3]=2, dp[4]=4, 因为我们配对的是第1个字符(**s[0]**)和第4个字符(**s[3]**); + 因此, 我们可以得出结论: 如果第**i**个字符和第i-1-dp[i-1]个字符是配对的, 则dp[i] = dp[i-1] + 2, 其中: i-1-dp[i-1] >= 1, 因为第0个字符没有任何意义; +4. 根据第3条规则来计算的话, 我们发现dp[5]=0, dp[6]=2, 但是显然, dp[6]应该为6才对, 但是我们发现可以将"(())"和"()"进行拼接, 即: dp[i] += dp[i-dp[i]], 即: dp[6] = 2 + dp[6-2] = 2 + dp[4] = 6 + +根据以上规则, 我们求解dp数组的结果为: [0, 0, 0, 2, 4, 0, 6, 0], 其中最长有效括号对的长度为6. 以下为图解: +![32.longest-valid-parentheses](../assets/problems/32.longest-valid-parentheses.png) + +## 关键点解析 + +1. 第3点特征, 需要检查的字符是s[i-1]和s[i-2-dp[i-1]], 根据定义可知: i-1 >= dp[i-1], 但是i-2不一定大于dp[i-1], 因此, 需要检查越界; +3. 第4点特征最容易遗漏, 还有就是不需要检查越界, 因为根据定义可知: i >= dp[i], 所以dp[i-dp[i]]的边界情况是dp[0]; + +## 思路(栈) +主要思路和常规的括号解法一样,遇到'('入栈,遇到')'出栈,并计算两个括号之间的长度。 +因为这个题存在非法括号对的情况且求是合法括号对的最大长度 所以有两个注意点是: +1. **栈中存的是符号的下标** +2. **当栈为空时且当前扫描到的符号是')'时,需要将这个符号入栈作为分割符** + +## 代码 + +* 语言支持: Python, javascript + +Python Code: +``` +class Solution: + def longestValidParentheses(self, s: str) -> int: + mlen = 0 + slen = len(s) + dp = [0] * (slen + 1) + for i in range(1, len(s) + 1): + # 有效的括号对不可能会以'('结尾的 + if s[i - 1] == '(': + continue + + left_paren = i - 2 - dp[i - 1] + if left_paren >= 0 and s[left_paren] == '(': + dp[i] = dp[i - 1] + 2 + + # 拼接有效括号对 + if dp[i - dp[i]]: + dp[i] += dp[i - dp[i]] + + # 更新最大有效扩对长度 + if dp[i] > mlen: + mlen = dp[i] + + return mlen +``` + +javascript code: +```js +// 用栈来解 +var longestValidParentheses = function(s) { + let stack = new Array() + let longest = 0 + stack.push(-1) + for(let i = 0; i < s.length; i++) { + if (s[i] === '(') { + stack.push(i) + } else { + stack.pop() + if (stack.length === 0) { + stack.push(i) + } else { + longest = Math.max(longest, i - stack[stack.length - 1]) + } + } + } + return longest +}; +``` + +## 扩展 + +1. 如果判断的不仅仅只有'('和')', 还有'[', ']', '{'和'}', 该怎么办? +2. 如果输出的不是长度, 而是任意一个最长有效括号对的字符串, 该怎么办? diff --git a/problems/322.coin-change.md b/problems/322.coin-change.md new file mode 100644 index 0000000..3839254 --- /dev/null +++ b/problems/322.coin-change.md @@ -0,0 +1,179 @@ + +## 题目地址 +https://leetcode.com/problems/coin-change/description/ + +## 题目描述 +``` +You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1. + +Example 1: + +Input: coins = [1, 2, 5], amount = 11 +Output: 3 +Explanation: 11 = 5 + 5 + 1 +Example 2: + +Input: coins = [2], amount = 3 +Output: -1 +Note: +You may assume that you have an infinite number of each kind of coin. + +``` +## 思路 + + +假如我们把coin逆序排列,然后逐个取,取到刚好不大于amout,依次类推。 + +``` +eg: 对于 [1,2,5] 组成 11 块 + +- 排序[5,2,1] + +- 取第一个5, 更新amout 为 11 - 5 = 6 (1⃣️) + 6 > 5 继续更新 为 6 - 5 = 1 (2⃣️) + 1 < 5 退出 + +- 取第二个2 + 1 < 2 退出 + +- 取最后一个元素,也就是1 + + 1 === 1 更新为 1 - 1 = 0 (3⃣️) + +- amout 为 0 退出 + + +因此结果是 3 +``` + +熟悉贪心算法的同学应该已经注意到了,这就是贪心算法,贪心算法更amount尽快地变得更小。 +`经验表明,贪心策略是正确的`。 注意,我说的是经验表明, 贪心算法也有可能出错。 就拿这道题目来说, +他也是不正确的! 比如 `coins = [1, 5, 11] amout = 15`, 因此这种做法有时候不靠谱,我们还是采用靠谱的做法. + +如果我们暴力求解,对于所有的组合都计算一遍,然后比较, 那么这样的复杂度是 2 的 n 次方(这个可以通过数学公式证明,这里不想啰嗦了), +这个是不可以接受的。那么我们是否可以动态规划解决呢?答案是可以,原因就是可以划分为子问题,子问题可以推导出原问题 + +对于动态规划我们可以先画一个二维表,然后观察,其是否可以用一维表代替。 +关于动态规划为什么要画表,我已经在[这篇文章](../thinkings/dynamic-programming.md)解释了 + +比较容易想到的是二维数组: + +```python +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + if amount < 0: + return - 1 + dp = [[amount + 1 for _ in range(len(coins) + 1)] + for _ in range(amount + 1)] + # 初始化第一行为0,其他为最大值(也就是amount + 1) + + for j in range(len(coins) + 1): + dp[0][j] = 0 + + for i in range(1, amount + 1): + for j in range(1, len(coins) + 1): + if i - coins[j - 1] >= 0: + dp[i][j] = min( + dp[i][j - 1], dp[i - coins[j - 1]][j] + 1) + else: + dp[i][j] = dp[i][j - 1] + + return -1 if dp[-1][-1] == amount + 1 else dp[-1][-1] + ``` + + **复杂度分析** +- 时间复杂度:$O(amonut * len(coins))$ +- 空间复杂度:$O(amount * len(coins))$ + +dp[i][j] 依赖于` dp[i][j - 1]`和 `dp[i - coins[j - 1]][j] + 1)` 这是一个优化的信号,我们可以将其优化到一维,具体见下方。 +## 关键点解析 + +- 动态规划 + +- 子问题 + +用dp[i] 来表示组成i块钱,需要最少的硬币数,那么 + +1. 第j个硬币我可以选择不拿 这个时候, 硬币数 = dp[i] + +2. 第j个硬币我可以选择拿 这个时候, 硬币数 = dp[i - coins[j]] + 1 + +- 和背包问题不同, 硬币是可以拿任意个 + +- 对于每一个 dp[i] 我们都选择遍历一遍 coin, 不断更新 dp[i] + +## 代码 + + +* 语言支持:JS,C++,Python3 + +JavaScript Code: +```js + +var coinChange = function(coins, amount) { + if (amount === 0) { + return 0; + } + const dp = Array(amount + 1).fill(Number.MAX_VALUE) + dp[0] = 0; + for (let i = 1; i < dp.length; i++) { + for (let j = 0; j < coins.length; j++) { + if (i - coins[j] >= 0) { + dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); + } + } + } + + return dp[dp.length - 1] === Number.MAX_VALUE ? -1 : dp[dp.length - 1]; + + +}; +``` +C++ Code: +> C++中采用INT_MAX,因此判断时需要加上`dp[a - coin] < INT_MAX`以防止溢出 +```C++ +class Solution { +public: + int coinChange(vector& coins, int amount) { + auto dp = vector(amount + 1, INT_MAX); + dp[0] = 0; + for (auto a = 1; a <= amount; ++a) { + for (const auto & coin : coins) { + if (a >= coin && dp[a - coin] < INT_MAX) { + dp[a] = min(dp[a], dp[a-coin] + 1); + } + } + } + return dp[amount] == INT_MAX ? -1 : dp[amount]; + } +}; +``` + +Python3 Code: + +```python +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + dp = [amount + 1] * (amount + 1) + dp[0] = 0 + + for i in range(1, amount + 1): + for j in range(len(coins)): + if i >= coins[j]: + dp[i] = min(dp[i], dp[i - coins[j]] + 1) + + return -1 if dp[-1] == amount + 1 else dp[-1] +``` + +**复杂度分析** +- 时间复杂度:$O(amonut * len(coins))$ +- 空间复杂度:$O(amount)$ + + +## 扩展 + +这是一道很简单描述的题目, 因此很多时候会被用到大公司的电面中。 + +相似问题: + +[518.coin-change-2](https://github.com/azl397985856/leetcode/blob/master/problems/518.coin-change-2.md) diff --git a/problems/328.odd-even-linked-list.md b/problems/328.odd-even-linked-list.md new file mode 100644 index 0000000..d91266f --- /dev/null +++ b/problems/328.odd-even-linked-list.md @@ -0,0 +1,122 @@ +## 题目地址 + +https://leetcode.com/problems/odd-even-linked-list/description/ + +## 题目描述 + +``` +Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes. + +You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity. + +Example 1: + +Input: 1->2->3->4->5->NULL +Output: 1->3->5->2->4->NULL +Example 2: + +Input: 2->1->3->5->6->4->7->NULL +Output: 2->3->6->7->1->5->4->NULL +Note: + +The relative order inside both the even and odd groups should remain as it was in the input. +The first node is considered odd, the second node even and so on ... +``` + +## 思路 + +符合直觉的想法是,先遍历一遍找出奇数的节点。然后再遍历一遍找出偶数节点,最后串起来。 + +但是有两个问题,如果不修改节点的话,需要借助额外的空间,空间复杂度是 N。如果修改的话,会对第二次遍历(遍历偶数节点)造成影响。 + +因此可以采用一种做法: 遍历一次,每一步同时修改两个节点就好了,这样就可以规避上面两个问题。 + +## 关键点解析 + +- 用虚拟节点来简化操作 + +- 循环的结束条件设置为 `odd && odd.next && even && even.next`, 不应该是`odd && even`, 否则需要记录一下奇数节点的最后一个节点,复杂了操作 + +## 代码 + +- 语言支持:JS,C++ + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=328 lang=javascript + * + * [328] Odd Even Linked List + * + * + */ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ +var oddEvenList = function(head) { + if (!head || !head.next) return head; + + const dummyHead1 = { + next: head + }; + const dummyHead2 = { + next: head.next + }; + + let odd = dummyHead1.next; + let even = dummyHead2.next; + + while (odd && odd.next && even && even.next) { + const oddNext = odd.next.next; + const evenNext = even.next.next; + + odd.next = oddNext; + even.next = evenNext; + + odd = oddNext; + even = evenNext; + } + + odd.next = dummyHead2.next; + + return dummyHead1.next; +}; +``` + +C++ Code: + +```C++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +class Solution { +public: + ListNode* oddEvenList(ListNode* head) { + if (head == nullptr) return head; + auto odd = head, evenHead = head->next, even = head->next; + // 因为“每次循环之后依然保持odd在even之前”,循环条件可以只判断even和even->next是否为空,修改odd和even的指向的操作也可以简化 + while (even != nullptr && even->next != nullptr) { + odd->next = even->next; + odd = odd->next; + even->next = odd->next; + even = even->next; + } + odd->next = evenHead; + return head; + } +}; +``` diff --git a/problems/33.search-in-rotated-sorted-array.md b/problems/33.search-in-rotated-sorted-array.md new file mode 100644 index 0000000..96d81c6 --- /dev/null +++ b/problems/33.search-in-rotated-sorted-array.md @@ -0,0 +1,149 @@ +## 题目地址 +https://leetcode.com/problems/search-in-rotated-sorted-array/ + +## 题目描述 + +``` +Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. + +(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]). + +You are given a target value to search. If found in the array return its index, otherwise return -1. + +You may assume no duplicate exists in the array. + +Your algorithm's runtime complexity must be in the order of O(log n). + +Example 1: + +Input: nums = [4,5,6,7,0,1,2], target = 0 +Output: 4 +Example 2: + +Input: nums = [4,5,6,7,0,1,2], target = 3 +Output: -1 + +``` + +## 思路 + +这是一个我在网上看到的前端头条技术终面的一个算法题。 + +题目要求时间复杂度为logn,因此基本就是二分法了。 这道题目不是直接的有序数组,不然就是easy了。 + +首先要知道,我们随便选择一个点,将数组分为前后两部分,其中一部分一定是有序的。 + +具体步骤: + +- 我们可以先找出mid,然后根据mid来判断,mid是在有序的部分还是无序的部分 + +假如mid小于start,则mid一定在右边有序部分。 +假如mid大于等于start, 则mid一定在左边有序部分。 + +> 注意等号的考虑 + +- 然后我们继续判断target在哪一部分, 我们就可以舍弃另一部分了 + +我们只需要比较target和有序部分的边界关系就行了。 比如mid在右侧有序部分,即[mid, end] +那么我们只需要判断 target >= mid && target <= end 就能知道target在右侧有序部分,我们就 +可以舍弃左边部分了(start = mid + 1), 反之亦然。 + +我们以([6,7,8,1,2,3,4,5], 4)为例讲解一下: + +![search-in-rotated-sorted-array-1](../assets/problems/search-in-rotated-sorted-array-1.jpg) + + +![search-in-rotated-sorted-array-1](../assets/problems/search-in-rotated-sorted-array-2.jpg) + + +## 关键点解析 + +- 二分法 +- 找出有序区间,然后根据target是否在有序区间舍弃一半元素 +## 代码 + +* 语言支持: Javascript,Python3 + +```js +/* + * @lc app=leetcode id=33 lang=javascript + * + * [33] Search in Rotated Sorted Array + */ +/** + * @param {number[]} nums + * @param {number} target + * @return {number} + */ +var search = function(nums, target) { + // 时间复杂度:O(logn) + // 空间复杂度:O(1) + // [6,7,8,1,2,3,4,5] + let start = 0; + let end = nums.length - 1; + + while (start <= end) { + const mid = start + ((end - start) >> 1); + if (nums[mid] === target) return mid; + + // [start, mid]有序 + + // ️⚠️注意这里的等号 + if (nums[mid] >= nums[start]) { + //target 在 [start, mid] 之间 + + // 其实target不可能等于nums[mid], 但是为了对称,我还是加上了等号 + if (target >= nums[start] && target <= nums[mid]) { + end = mid - 1; + } else { + //target 不在 [start, mid] 之间 + start = mid + 1; + } + } else { + // [mid, end]有序 + + // target 在 [mid, end] 之间 + if (target >= nums[mid] && target <= nums[end]) { + start = mid + 1; + } else { + // target 不在 [mid, end] 之间 + end = mid - 1; + } + } + } + + return -1; +}; +``` +Python3 Code: +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + """用二分法,先判断左右两边哪一边是有序的,再判断是否在有序的列表之内""" + if len(nums) <= 0: + return -1 + + left = 0 + right = len(nums) - 1 + while left < right: + mid = (right - left) // 2 + left + if nums[mid] == target: + return mid + + # 如果中间的值大于最左边的值,说明左边有序 + if nums[mid] > nums[left]: + if nums[left] <= target <= nums[mid]: + right = mid + else: + # 这里 +1,因为上面是 <= 符号 + left = mid + 1 + # 否则右边有序 + else: + # 注意:这里必须是 mid+1,因为根据我们的比较方式,mid属于左边的序列 + if nums[mid+1] <= target <= nums[right]: + left = mid + 1 + else: + right = mid + + return left if nums[left] == target else -1 +``` diff --git a/problems/334.increasing-triplet-subsequence.md b/problems/334.increasing-triplet-subsequence.md new file mode 100644 index 0000000..7c359e3 --- /dev/null +++ b/problems/334.increasing-triplet-subsequence.md @@ -0,0 +1,106 @@ + +## 题目地址 +https://leetcode.com/problems/increasing-triplet-subsequence/description/ + +## 题目描述 + +``` +Given an unsorted array return whether an increasing subsequence of length 3 exists or not in the array. + +Formally the function should: + +Return true if there exists i, j, k +such that arr[i] < arr[j] < arr[k] given 0 ≤ i < j < k ≤ n-1 else return false. +Note: Your algorithm should run in O(n) time complexity and O(1) space complexity. + +Example 1: + +Input: [1,2,3,4,5] +Output: true +Example 2: + +Input: [5,4,3,2,1] +Output: false +``` + +## 思路 +这道题是求解顺序数字是否有三个递增的排列, 注意这里没有要求连续的,因此诸如滑动窗口的思路是不可以的。 +题目要求O(n)的时间复杂度和O(1)的空间复杂度,因此暴力的做法就不用考虑了。 + +我们的目标就是`依次`找到三个数字,其顺序是递增的。因此我们的做法可以是依次遍历, +然后维护三个变量,分别记录最小值,第二小值,第三小值。只要我们能够填满这三个变量就返回true,否则返回false。 + +![334.increasing-triplet-subsequence](../assets/problems/334.increasing-triplet-subsequence.png) +## 关键点解析 + +- 维护三个变量,分别记录最小值,第二小值,第三小值。只要我们能够填满这三个变量就返回true,否则返回false + +## 代码 +```js + + +/* + * @lc app=leetcode id=334 lang=javascript + * + * [334] Increasing Triplet Subsequence + * + * https://leetcode.com/problems/increasing-triplet-subsequence/description/ + * + * algorithms + * Medium (39.47%) + * Total Accepted: 89.6K + * Total Submissions: 226.6K + * Testcase Example: '[1,2,3,4,5]' + * + * Given an unsorted array return whether an increasing subsequence of length 3 + * exists or not in the array. + * + * Formally the function should: + * + * Return true if there exists i, j, k + * such that arr[i] < arr[j] < arr[k] given 0 ≤ i < j < k ≤ n-1 else return + * false. + * + * Note: Your algorithm should run in O(n) time complexity and O(1) space + * complexity. + * + * + * Example 1: + * + * + * Input: [1,2,3,4,5] + * Output: true + * + * + * + * Example 2: + * + * + * Input: [5,4,3,2,1] + * Output: false + * + * + * + */ +/** + * @param {number[]} nums + * @return {boolean} + */ +var increasingTriplet = function(nums) { + if (nums.length < 3) return false; + let n1 = Number.MAX_VALUE; + let n2 = Number.MAX_VALUE; + + for(let i = 0; i < nums.length; i++) { + if (nums[i] <= n1) { + n1 = nums[i] + } else if (nums[i] <= n2) { + n2 = nums[i] + } else { + return true; + } + } + + return false; +}; +``` diff --git a/problems/335.self-crossing.md b/problems/335.self-crossing.md new file mode 100644 index 0000000..0ff9482 --- /dev/null +++ b/problems/335.self-crossing.md @@ -0,0 +1,117 @@ +## 题目地址(335. 路径交叉) + +https://leetcode-cn.com/problems/self-crossing/ + +## 题目描述 + +``` +给定一个含有 n 个正数的数组 x。从点 (0,0) 开始,先向北移动 x[0] 米,然后向西移动 x[1] 米,向南移动 x[2] 米,向东移动 x[3] 米,持续移动。也就是说,每次移动后你的方位会发生逆时针变化。 + +编写一个 O(1) 空间复杂度的一趟扫描算法,判断你所经过的路径是否相交。 + +  + +示例 1: + +┌───┐ +│   │ +└───┼──> +    │ + +输入: [2,1,1,2] +输出: true +示例 2: + +┌──────┐ +│      │ +│ +│ +└────────────> + +输入: [1,2,3,4] +输出: false +示例 3: + +┌───┐ +│   │ +└───┼> + +输入: [1,1,1,1] +输出: true + +``` + +## 思路 + +符合直觉的做法是$O(N)$时间和空间复杂度的算法。这种算法非常简单,但是题目要求我们使用空间复杂度为$O(1)$的做法。 + +关于空间复杂度为$O(N)$的算法可以参考我之前的[874.walking-robot-simulation](https://github.com/azl397985856/leetcode/blob/be15d243a3b93d7efa731d0589a54a63cbff61ae/problems/874.walking-robot-simulation.md)。 思路基本是类似,只不过 obstacles(障碍物)不是固定的,而是我们不断遍历的时候动态生成的,我们每遇到一个点,就将其标记为 obstacle。随着算法的进行,我们的 obstacles 逐渐增大,最终和 N 一个量级。 + +我们考虑进行优化。我们仔细观察发现,如果想让其不相交,从大的范围来看只有两种情况: + +1. 我们画的圈不断增大。 +2. 我们画的圈不断减少。 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gbepb3y3uwj30te1dagn5.jpg) +(有没有感觉像迷宫?) + +这样我们会发现,其实我们画最新一笔的时候,并不是之前画的所有的都需要考虑,我们只需要最近的几个就可以了,实际上是最近的五个,不过不知道也没关系,我们稍后会讲解。 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gbepcb2ojwj30to0lamxm.jpg) + +红色部分指的是我们需要考虑的,而剩余没有被红色标注的部分则无需考虑。不是因为我们无法与之相交,而是我们`一旦与之相交,则必然我们也一定会与红色标记部分相交`。 + +然而我们画的方向也是不用考虑的。比如我当前画的方向是从左到右,那和我画的方向是从上到下有区别么?在这里是没区别的,不信我帮你将上图顺时针旋转 90 度看一下: + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbepgmzlopj30mk1cwwfn.jpg) + +方向对于我们考虑是否相交没有差别。 + +当我们仔细思考的时候,会发现其实相交的情况只有以下几种: + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gbepi1aegtj30ro0o6aat.jpg) + +这个时候代码就呼之欲出了。 + +- 我们只需要遍历数组 x,假设当前是第 i 个元素。 +- 如果 x[i] >= x[i - 2] and x[i - 1] <= x[i - 3],则相交(第一种情况) +- 如果 x[i - 1] <= x[i - 3] and x[i - 2] <= x[i],则相交(第二种情况) +- 如果 i > 3 and x[i - 1] == x[i - 3] and x[i] + x[i - 4] == x[i - 2],则相交(第三种情况) +- 如果 i > 4 and x[i] + x[i - 4] >= x[i - 2] and x[i - 1] >= x[i - 3] - x[i - 5] \ + and x[i - 1] <= x[i - 3] and x[i - 2] >= x[i - 4] and x[i - 3] >= x[i - 5] ,则相交(第四种情况) +- 否则不相交 + +## 关键点解析 + +- 一定要画图辅助 +- 对于这种$O(1)$空间复杂度有固定的套路。常见的有: + +1. 直接修改原数组 +2. 滑动窗口(当前状态并不是和之前所有状态有关,而是仅和某几个有关)。 + +我们采用的是滑动窗口。但是难点就在于我们怎么知道当前状态和哪几个有关。对于这道题来说,画图或许可以帮助你打开思路。另外面试的时候说出$O(N)$的思路也不失为一个帮助你冷静分析问题的手段。 + +## 代码 + +代码支持:Python3 + +Python3 Code: + +```python +class Solution: + def isSelfCrossing(self, x: List[int]) -> bool: + n = len(x) + if n < 4: + return False + for i in range(3, n): + if x[i] >= x[i - 2] and x[i - 1] <= x[i - 3]: + return True + if x[i - 1] <= x[i - 3] and x[i - 2] <= x[i]: + return True + if i > 3 and x[i - 1] == x[i - 3] and x[i] + x[i - 4] == x[i - 2]: + return True + if i > 4 and x[i] + x[i - 4] >= x[i - 2] and x[i - 1] >= x[i - 3] - x[i - 5] \ + and x[i - 1] <= x[i - 3] and x[i - 2] >= x[i - 4] and x[i - 3] >= x[i - 5]: + return True + return False +``` diff --git a/problems/342.power-of-four.md b/problems/342.power-of-four.md new file mode 100644 index 0000000..b07b83f --- /dev/null +++ b/problems/342.power-of-four.md @@ -0,0 +1,131 @@ +## 题目地址 + +https://leetcode.com/problems/power-of-four/description/ + +## 题目描述 + +``` +Given an integer (signed 32 bits), write a function to check whether it is a power of 4. + +Example 1: + +Input: 16 +Output: true +Example 2: + +Input: 5 +Output: false +Follow up: Could you solve it without loops/recursion? + +``` + +## 思路 + +符合直觉的做法是不停除以 4 直到不能整除,然后判断是否为 1 即可。 代码如下: + +```js +while (num && num % 4 == 0) { + num /= 4; +} +return num == 1; +``` + +但是这道题目有一个 follow up: “你是否可以不使用循环/递归完成”。因此我们需要换种思路。 + +我们先来看下,4 的幂次方用 2 进制表示是什么样的. + +![263.342.power-of-four-1](../assets/problems/342.power-of-four-1.png) + +发现规律: 4 的幂次方的二进制表示 1 的位置都是在奇数位(且不在最低位),其他位置都为 0 + +我们还可以发现: 2 的幂次方的特点是最低位之外,其他位置有且仅有一个 1(1 可以在任意位置) + +我们进一步分析,如果一个数字是四的幂次方,那么只需要满足: + +1. 是 2 的幂次方, 就能保证最低位之外,其他位置有且仅有一个 1 +2. 这个 1 不在偶数位置,一定在奇数位置 + +对于第一点,如果保证一个数字是 2 的幂次方呢? 显然不能不停除以 2,看结果是否等于 1,这样就循环了。 +我们可以使用一个 trick, 如果一个数字 n 是 2 的幂次方,那么 n & (n - 1) 一定等于 0, +这个可以作为思考题,大家思考一下。 + +对于第二点,我们可以取一个特殊数字,这个特殊数字,奇数位置都是 1,偶数位置都是 0,然后和这个特殊数字 +`求与`, 如果等于本身,那么毫无疑问,这个 1 不再偶数位置,一定在奇数位置,因为如果在偶数位置,`求与`的结果就是 0 了 +题目要求 n 是 32 位有符号整形,那么我们的特殊数字就应该是`01010101010101010101010101010101`(不用数了,一共 32 位)。 + +![263.342.power-of-four-2](../assets/problems/342.power-of-four-2.png) + +如上图,64和这个特殊数字求与,得到的是本身。 8 是 2的次方,但是不是4的次方,我们求与结果就是0了。 + +为了体现自己的逼格,我们可以使用计算器,来找一个逼格比较高的数字,这里我选了十六进制,结果是`0x55555555`。 + +![263.342.power-of-four](../assets/problems/342.power-of-four.png) + +代码见下方代码区。 + +说实话,这种做法不容易想到,其实还有一种方法。 +如果一个数字是 4 的幂次方,那么只需要满足: + +1. 是二的倍数 +2. 减去 1 是三的倍数 + +代码如下: + +```js +return num > 0 && (num & (num - 1)) === 0 && (num - 1) % 3 === 0; +``` + +## 关键点 + +- 数论 +- 2的幂次方特点(数学性质以及二进制表示) +- 4的幂次方特点(数学性质以及二进制表示) + +## 代码 + +语言支持:JS, Python + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=342 lang=javascript + * + * [342] Power of Four + */ +/** + * @param {number} num + * @return {boolean} + */ +var isPowerOfFour = function(num) { + // tag: 数论 + + if (num === 1) return true; + if (num < 4) return false; + + if ((num & (num - 1)) !== 0) return false; + + return (num & 0x55555555) === num; +}; +``` + +Python Code: + +```python +class Solution: + def isPowerOfFour(self, num: int) -> bool: + if num == 1: + return True + elif num < 4: + return False + else: + if not num & (num-1) == 0: + return False + else: + return num & 0x55555555 == num + + # 另一种解法:将数字转化为二进制表示的字符串,利用字符串的相关操作进行判断 + def isPowerOfFour(self, num: int) -> bool: + binary_num = bin(num)[2:] + return binary_num.strip('0') == '1' and len(binary_num) % 2 == 1 +``` diff --git a/problems/343.integer-break.md b/problems/343.integer-break.md new file mode 100644 index 0000000..6f4e735 --- /dev/null +++ b/problems/343.integer-break.md @@ -0,0 +1,191 @@ +## 题目地址(343. 整数拆分) + +https://leetcode-cn.com/problems/integer-break/ + +## 题目描述 + +给定一个正整数  n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 + +示例 1: + +输入: 2 +输出: 1 +解释: 2 = 1 + 1, 1 × 1 = 1。 +示例  2: + +输入: 10 +输出: 36 +解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 +说明: 你可以假设  n  不小于 2 且不大于 58。 + +## 思路 + +希望通过这篇题解让大家知道“题解区的水有多深”,让大家知道“什么才是好的题解”。 + +我看了很多人的题解直接就是两句话,然后跟上代码: + +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [1] * (n + 1) + for i in range(3, n + 1): + for j in range(1, i): + dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) + return dp[n] +``` + +这种题解说实话,只针对那些”自己会, 然后去题解区看看有没有新的更好的解法的人“。但是大多数看题解的人是那种`自己没思路,不会做的人`。那么这种题解就没什么用了。 + +我认为`好的题解应该是新手友好的,并且能够将解题人思路完整展现的题解`。比如看到这个题目,我首先想到了什么(对错没有关系),然后头脑中经过怎么样的筛选将算法筛选到具体某一个或某几个。我的最终算法是如何想到的,有没有一些先行知识。 + +当然我也承认自己有很多题解也是直接给的答案,这对很多人来说用处不大,甚至有可能有反作用,给他们一种”我已经会了“的假象。实际上他们根本不懂解题人本身原本的想法, 也许是写题解的人觉得”这很自然“,也可能”只是为了秀技“。 + +Ok,下面来讲下`我是如何解这道题的`。 + +### 抽象 + +首先看到这道题,自然而然地先对问题进行抽象,这种抽象能力是必须的。LeetCode 实际上有很多这种穿着华丽外表的题,当你把这个衣服扒开的时候,会发现都是差不多的,甚至两个是一样的,这样的例子实际上有很多。 就本题来说,就有一个剑指 Offer 的原题[《剪绳子》](https://leetcode-cn.com/problems/jian-sheng-zi-lcof/)和其本质一样,只是换了描述方式。类似的有力扣 137 和 645 等等,大家可以自己去归纳总结。 + +> 137 和 645 我贴个之前写的题解 https://leetcode-cn.com/problems/single-number/solution/zhi-chu-xian-yi-ci-de-shu-xi-lie-wei-yun-suan-by-3/ + +**培养自己抽象问题的能力,不管是在算法上还是工程上。** 务必记住这句话! + +数学是一门非常抽象的学科,同时也很方便我们抽象问题。为了显得我的题解比较高级,引入一些你们看不懂的数学符号也是很有必要的(开玩笑,没有什么高级数学符号啦)。 + +> 实际上这道题可以用纯数学角度来解,但是我相信大多数人并不想看。即使你看了,大多人的感受也是“好 nb,然而并没有什么用”。 + +这道题抽象一下就是: + +令: +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu3kolxoyj305o03cwef.jpg) +(图 1) +求: +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu3jy6mxkj305o0360sp.jpg) +(图 2) + +## 第一直觉 + +经过上面的抽象,我的第一直觉这可能是一个数学题,我回想了下数学知识,然后用数学法 AC 了。 数学就是这么简单平凡且枯燥。 + +然而如果没有数学的加持的情况下,我继续思考怎么做。我想是否可以枚举所有的情况(如图 1),然后对其求最大值(如图 2)。 + +问题转化为如何枚举所有的情况。经过了几秒钟的思考,我发现这是一个很明显的递归问题。 具体思考过程如下: + +- 我们将原问题抽象为 f(n) +- 那么 f(n) 等价于 max(1 \* fn(n - 1), 2 \* f(n - 2), ..., (n - 1) \* f(1))。 + +用数学公式表示就是: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu3swzc9ej30co03yaa4.jpg) +(图 3) + +截止目前,是一点点数学 + 一点点递归,我们继续往下看。现在问题是不是就很简单啦?直接翻译图三为代码即可,我们来看下这个时候的代码: + +```python +class Solution: + def integerBreak(self, n: int) -> int: + if n == 2: return 1 + res = 0 + for i in range(1, n): + res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) + return res +``` + +毫无疑问,超时了。原因很简单,就是算法中包含了太多的重复计算。如果经常看我的题解的话,这句话应该不陌生。我随便截一个我之前讲过这个知识点的图。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu3zfz89jj313p0u0wnj.jpg) +(图 4) + +> 原文链接:https://github.com/azl397985856/leetcode/blob/master/thinkings/dynamic-programming.md + +大家可以尝试自己画图理解一下。 + +> 看到这里,有没有种殊途同归的感觉呢? + +## 考虑优化 + +如上,我们可以考虑使用记忆化递归的方式来解决。只是用一个 hashtable 存储计算过的值即可。 + +```python +class Solution: + @lru_cache() + def integerBreak(self, n: int) -> int: + if n == 2: return 1 + res = 0 + for i in range(1, n): + res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) + return res +``` + +为了简单起见(偷懒起见),我直接用了 lru_cache 注解, 上面的代码是可以 AC 的。 + +## 动态规划 + +看到这里的同学应该发现了,这个套路是不是很熟悉?下一步就是将其改造成动态规划了。 + +如图 4,我们的思考方式是从顶向下,这符合人们思考问题的方式。将其改造成如下图的自底向上方式就是动态规划。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu4a1grbvj31eq0r0wj8.jpg) +(图 5) + +现在再来看下文章开头的代码: + +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [1] * (n + 1) + for i in range(3, n + 1): + for j in range(1, i): + dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) + return dp[n] +``` + +dp table 存储的是图 3 中 f(n)的值。一个自然的想法是令 dp[i] 等价于 f(i)。而由于上面分析了原问题等价于 f(n),那么很自然的原问题也等价于 dp[n]。 + +而 dp[i]等价于 f(i),那么上面针对 f(i) 写的递归公式对 dp[i] 也是适用的,我们拿来试试。 + +``` +// 关键语句 +res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) +``` + +翻译过来就是: + +``` +dp[i] = max(dp[i], max(i * dp(n - i),i * (n - i))) +``` + +而这里的 n 是什么呢?我们说了`dp是自底向下的思考方式`,那么在达到 n 之前是看不到整体的`n` 的。因此这里的 n 实际上是 1,2,3,4... n。 + +自然地,我们用一层循环来生成上面一系列的 n 值。接着我们还要生成一系列的 i 值,注意到 n - i 是要大于 0 的,因此 i 只需要循环到 n - 1 即可。 + +思考到这里,我相信上面的代码真的是`不难得出`了。 + +## 关键点 + +- 数学抽象 +- 递归分析 +- 记忆化递归 +- 动态规划 + +## 代码 + +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [1] * (n + 1) + for i in range(3, n + 1): + for j in range(1, i): + dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) + return dp[n] +``` + +## 总结 + +培养自己的解题思维很重要, 不要直接看别人的答案。而是要将别人的东西变成自己的, 而要做到这一点,你就要知道“他们是怎么想到的”,“想到这点是不是有什么前置知识”,“类似题目有哪些”。 + +最优解通常不是一下子就想到了,这需要你在不那么优的解上摔了很多次跟头之后才能记住的。因此在你没有掌握之前,不要直接去看最优解。 在你掌握了之后,我不仅鼓励你去写最优解,还鼓励去一题多解,从多个解决思考问题。 到了那个时候, 萌新也会惊讶地呼喊“哇塞, 这题还可以这么解啊?”。 你也会低调地发出“害,解题就是这么简单平凡且枯燥。”的声音。 + +## 扩展 + +正如我开头所说,这种套路实在是太常见了。希望大家能够识别这种问题的本质,彻底掌握这种套路。另外我对这个套路也在我的新书《LeetCode 题解》中做了介绍,本书目前刚完成草稿的编写,如果你想要第一时间获取到我们的题解新书,那么请发送邮件到 `azl397985856@gmail.com`,标题著明“书籍《LeetCode 题解》预定”字样。。 diff --git a/problems/349.intersection-of-two-arrays.md b/problems/349.intersection-of-two-arrays.md new file mode 100644 index 0000000..17b255e --- /dev/null +++ b/problems/349.intersection-of-two-arrays.md @@ -0,0 +1,126 @@ + +## 题目地址 +https://leetcode.com/problems/intersection-of-two-arrays/description/ + +## 题目描述 + +``` +Given two arrays, write a function to compute their intersection. + +Example 1: + +Input: nums1 = [1,2,2,1], nums2 = [2,2] +Output: [2] +Example 2: + +Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4] +Output: [9,4] +Note: + +Each element in the result must be unique. +The result can be in any order. + +``` + +## 思路 + +先遍历第一个数组,将其存到hashtable中, +然后遍历第二个数组,如果在hashtable中存在就push到return,然后清空hashtable即可。 + +## 关键点解析 + +无 + +## 代码 + +* 语言支持:JS, Python + +Javascript Code: + +```js +/* + * @lc app=leetcode id=349 lang=javascript + * + * [349] Intersection of Two Arrays + * + * https://leetcode.com/problems/intersection-of-two-arrays/description/ + * + * algorithms + * Easy (53.11%) + * Total Accepted: 203.6K + * Total Submissions: 380.9K + * Testcase Example: '[1,2,2,1]\n[2,2]' + * + * Given two arrays, write a function to compute their intersection. + * + * Example 1: + * + * + * Input: nums1 = [1,2,2,1], nums2 = [2,2] + * Output: [2] + * + * + * + * Example 2: + * + * + * Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4] + * Output: [9,4] + * + * + * Note: + * + * + * Each element in the result must be unique. + * The result can be in any order. + * + * + * + * + */ +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number[]} + */ +var intersection = function(nums1, nums2) { + const visited = {}; + const ret = []; + for(let i = 0; i < nums1.length; i++) { + const num = nums1[i]; + + visited[num] = num; + } + + for(let i = 0; i < nums2.length; i++) { + const num = nums2[i]; + + if (visited[num] !== undefined) { + ret.push(num); + visited[num] = undefined; + } + } + + return ret; + +}; +``` + +Python Code: + +```python +class Solution: + def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: + visited, result = {}, [] + for num in nums1: + visited[num] = num + for num in nums2: + if num in visited: + result.append(num) + visited.pop(num) + return result + + # 另一种解法:利用 Python 中的集合进行计算 + def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: + return set(nums1) & set(nums2) +``` diff --git a/problems/365.water-and-jug-problem.md b/problems/365.water-and-jug-problem.md new file mode 100644 index 0000000..6db3684 --- /dev/null +++ b/problems/365.water-and-jug-problem.md @@ -0,0 +1,208 @@ + +## 题目地址 +https://leetcode.com/problems/water-and-jug-problem/description/ + +## 题目描述 + +``` +You are given two jugs with capacities x and y litres. There is an infinite amount of water supply available. You need to determine whether it is possible to measure exactly z litres using these two jugs. + +If z liters of water is measurable, you must have z liters of water contained within one or both buckets by the end. + +Operations allowed: + +Fill any of the jugs completely with water. +Empty any of the jugs. +Pour water from one jug into another till the other jug is completely full or the first jug itself is empty. +Example 1: (From the famous "Die Hard" example) + +Input: x = 3, y = 5, z = 4 +Output: True +Example 2: + +Input: x = 2, y = 6, z = 5 +Output: False + +``` + + +## BFS(超时) + +### 思路 + +两个水壶的水我们考虑成状态,然后我们不断进行倒的操作,改变状态。那么初始状态就是(0 0) 目标状态就是 (any, z)或者 (z, any),其中any 指的是任意升水。 + + +已题目的例子,其过程示意图,其中括号表示其是由哪个状态转移过来的: + +0 0 +3 5(0 0) 3 0 (0 0 )0 5(0 0) +3 2(0 5) 0 3(0 0) +0 2(3 2) +2 0(0 2) +2 5(2 0) +3 4(2 5) bingo + +### 代码 + +```python +class Solution: + def canMeasureWater(self, x: int, y: int, z: int) -> bool: + if x + y < z: + return False + queue = [(0, 0)] + seen = set((0, 0)) + + while(len(queue) > 0): + a, b = queue.pop(0) + if a ==z or b == z or a + b == z: + return True + states = set() + + states.add((x, b)) + states.add((a, y)) + states.add((0, b)) + states.add((a, 0)) + states.add((min(x, b + a), 0 if b < x - a else b - (x - a))) + states.add((0 if a + b < y else a - (y - b), min(b + a, y))) + for state in states: + if state in seen: + continue; + queue.append(state) + seen.add(state) + return False +``` + +**复杂度分析** + +- 时间复杂度:由于状态最多有$O((x + 1) * (y + 1))$ 种,因此总的时间复杂度为$O(x * y)$。 +- 空间复杂度:我们使用了队列来存储状态,set 存储已经访问的元素,空间复杂度和状态数目一致,因此空间复杂度是$O(x * y)$。 + +上面的思路很直观,但是很遗憾这个算法在 LeetCode 的表现是 TLE(Time Limit Exceeded)。不过如果你能在真实面试中写出这样的算法,我相信大多数情况是可以过关的。 + +我们来看一下有没有别的解法。实际上,上面的算法就是一个标准的 BFS。如果从更深层次去看这道题,会发现这道题其实是一道纯数学问题,类似的纯数学问题在 LeetCode 中也会有一些,不过大多数这种题目,我们仍然可以采取其他方式 AC。那么让我们来看一下如何用数学的方式来解这个题。 + +## 数学法 - 最大公约数 + +### 思路 + +这是一道关于`数论`的题目,确切地说是关于`裴蜀定理`(英语:Bézout's identity)的题目。 + +摘自wiki的定义: + +``` +对任意两个整数 a、b,设 d是它们的最大公约数。那么关于未知数 x和 y的线性丢番图方程(称为裴蜀等式): + +ax+by=m + +有整数解 (x,y) 当且仅当 m是 d的整数倍。裴蜀等式有解时必然有无穷多个解。 + +``` + +因此这道题可以完全转化为`裴蜀定理`。还是以题目给的例子`x = 3, y = 5, z = 4`,我们其实可以表示成`3 * 3 - 1 * 5 = 4`, 即`3 * x - 1 * y = z`。我们用a和b分别表示3 +升的水壶和5升的水壶。那么我们可以: + + +- 倒满a(**1**) +- 将a倒到b +- 再次倒满a(**2**) +- 再次将a倒到b(a这个时候还剩下1升) +- 倒空b(**-1**) +- 将剩下的1升倒到b +- 将a倒满(**3**) +- 将a倒到b +- b此时正好是4升 + +上面的过程就是`3 * x - 1 * y = z`的具体过程解释。 + +**也就是说我们只需要求出x和y的最大公约数d,并判断z是否是d的整数倍即可。** + + +### 代码 + +代码支持:Python3,JavaScript + + +Python Code: + +```python +class Solution: + def canMeasureWater(self, x: int, y: int, z: int) -> bool: + if x + y < z: + return False + + if (z == 0): + return True + + if (x == 0): + return y == z + + if (y == 0): + return x == z + + def GCD(a, b): + smaller = min(a, b) + while smaller: + if a % smaller == 0 and b % smaller == 0: + return smaller + smaller -= 1 + + return z % GCD(x, y) == 0 +``` + +JavaScript: + + +```js +/** + * @param {number} x + * @param {number} y + * @param {number} z + * @return {boolean} + */ +var canMeasureWater = function(x, y, z) { + if (x + y < z) return false; + + if (z === 0) return true; + + if (x === 0) return y === z; + + if (y === 0) return x === z; + + function GCD(a, b) { + let min = Math.min(a, b); + while (min) { + if (a % min === 0 && b % min === 0) return min; + min--; + } + return 1; + } + + return z % GCD(x, y) === 0; +}; +``` + +实际上求最大公约数还有更好的方式,比如辗转相除法: + +```python +def GCD(a, b): + if b == 0: return a + return GCD(b, a % b) +``` + +**复杂度分析** + +- 时间复杂度:$O(log(max(a, b)))$ +- 空间复杂度:空间复杂度取决于递归的深度,因此空间复杂度为 $O(log(max(a, b)))$ + + +## 关键点分析 + +- 数论 +- 裴蜀定理 + +更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经接近30K star啦。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) diff --git a/problems/371.sum-of-two-integers.md b/problems/371.sum-of-two-integers.md new file mode 100644 index 0000000..a52df6a --- /dev/null +++ b/problems/371.sum-of-two-integers.md @@ -0,0 +1,61 @@ + +## 题目地址 +https://leetcode.com/problems/sum-of-two-integers/description/ + +## 题目描述 + +``` +Calculate the sum of two integers a and b, but you are not allowed to use the operator + and -. + +Example 1: + +Input: a = 1, b = 2 +Output: 3 +Example 2: + +Input: a = -2, b = 3 +Output: 1 + +``` + +## 思路 + +不能使用加减法来求加法。 我们只能朝着位元算的角度来思考了。 + +由于`异或`是`相同则位0,不同则位1`,因此我们可以把异或看成是一种不进位的加减法。 + +![371.sum-of-two-integers-1](../assets/problems/371.sum-of-two-integers-1.png) + +由于`与`是`全部位1则位1,否则位0`,因此我们可以求与之后左移一位来表示进位。 + +![371.sum-of-two-integers-2](../assets/problems/371.sum-of-two-integers-2.png) + +然后我们对上述两个元算结果递归求解即可。 递归的结束条件就是其中一个为0,我们直接返回另一个。 + +## 关键点解析 + +- 位运算 +- 异或是一种不进位的加减法 +- 求与之后左移一位来可以表示进位 + +## 代码 +```js +/* + * @lc app=leetcode id=371 lang=javascript + * + * [371] Sum of Two Integers + */ +/** + * @param {number} a + * @param {number} b + * @return {number} + */ +var getSum = function(a, b) { + if (a === 0) return b; + + if (b === 0) return a; + + return getSum(a ^ b, (a & b) << 1); +}; +``` + diff --git a/problems/378.kth-smallest-element-in-a-sorted-matrix.md b/problems/378.kth-smallest-element-in-a-sorted-matrix.md new file mode 100644 index 0000000..8054da9 --- /dev/null +++ b/problems/378.kth-smallest-element-in-a-sorted-matrix.md @@ -0,0 +1,155 @@ +## 题目地址 + +https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/ + +## 题目描述 + +``` +Given a n x n matrix where each of the rows and columns are sorted in ascending order, find the kth smallest element in the matrix. + +Note that it is the kth smallest element in the sorted order, not the kth distinct element. + +Example: + +matrix = [ + [ 1, 5, 9], + [10, 11, 13], + [12, 13, 15] +], +k = 8, + +return 13. +Note: +You may assume k is always valid, 1 ≤ k ≤ n2. +``` + +## 思路 + +显然用大顶堆可以解决,时间复杂度 Klogn n 为总的数字个数, +但是这种做法没有利用题目中 sorted matrix 的特点,因此不是一种好的做法. + +一个巧妙的方法是二分法,我们分别从第一个和最后一个向中间进行扫描,并且计算出中间的数值与数组中的进行比较, +可以通过计算中间值在这个数组中排多少位,然后得到比中间值小的或者大的数字有多少个,然后与 k 进行比较,如果比 k 小则说明中间值太小了,则向后移动,否则向前移动。 + +这个题目的二分确实很难想,我们来一步一步解释。 + +最普通的二分法是有序数组中查找指定值(或者说满足某个条件的值)。由于是有序的,我们可以根据索引关系来确定大小关系, +因此这种思路比较直接,但是对于这道题目索引大小和数字大小没有直接的关系,因此这种二分思想就行不通了。 + +![378.kth-smallest-element-in-a-sorted-matrix-1](../assets/problems/378.kth-smallest-element-in-a-sorted-matrix-1.jpg) + +(普通的基于索引判断的二分法) + +- 我们能够找到矩阵中最大的元素(右下角)和最小的元素(左上角)。我们可以求出值的中间,而不是上面那种普通二分法的索引的中间。 + +![378.kth-smallest-element-in-a-sorted-matrix-3](../assets/problems/378.kth-smallest-element-in-a-sorted-matrix-3.jpg) + +- 找到中间值之后,我们可以拿这个值去计算有多少元素是小于等于它的。 +具体方式就是比较行的最后一列,如果中间值比最后一列大,说明中间元素肯定大于这一行的所有元素。 否则我们从后往前遍历直到不大于。 + +![378.kth-smallest-element-in-a-sorted-matrix-2](../assets/problems/378.kth-smallest-element-in-a-sorted-matrix-2.jpg) + +- 上一步我们会计算一个count,我们拿这个count和k进行比较 + +- 如果count小于k,说明我们选择的中间值太小了,肯定不符合条件,我们需要调整左区间为mid + 1 + +- 如果count大于k,说明我们选择的中间值正好或者太大了。我们调整右区间 mid + +> 由于count大于k 也可能我们选择的值是正好的, 因此这里不能调整为mid - 1, 否则可能会得不到结果 + +- 最后直接返回start, end, 或者 mid都可以,因此三者最终会收敛到矩阵中的一个元素,这个元素也正是我们要找的元素。 + +整个计算过程是这样的: + +![378.kth-smallest-element-in-a-sorted-matrix-4](../assets/problems/378.kth-smallest-element-in-a-sorted-matrix-4.jpg) + +这里有一个大家普遍都比较疑惑的点,也是我当初非常疑惑,困扰我很久的点, leetcode评论区也有很多人来问,就是“能够确保最终我们找到的元素一定在矩阵中么?” + +答案是可以, `相等的时候一定在matrix里面。 因为原问题一定有解,找下界使得start不断的逼近于真实的元素`. + +我是看了评论区一个大神的评论才明白的,以下是[@GabrielaSong](https://leetcode.com/gabrielasong/)的评论原文: + +``` +The lo we returned is guaranteed to be an element in the matrix is because: +Let us assume element m is the kth smallest number in the matrix, and x is the number of element m in the matrix. +When we are about to reach convergence, if mid=m-1, its count value (the number of elements which are <= mid) would be k-x, +so we would set lo as (m-1)+1=m, in this case the hi will finally reach lo; +and if mid=m+1, its count value would be k+x-1, so we would set hi as m+1, in this case the lo will finally reach m. +To sum up, because the number lo found by binary search find is exactly the element which has k number of elements in the matrix that are <= lo, + The equal sign guarantees there exists and only exists one number in range satisfying this condition. + So lo must be the only element satisfying this element in the matrix. + +``` + +更多解释,可以参考[leetcode discuss](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/discuss/85173/Share-my-thoughts-and-Clean-Java-Code) + +> 如果是普通的二分查找,我们是基于索引去找,因此不会有这个问题。 + + +## 关键点解析 + +- 二分查找 + +- 有序矩阵的套路(文章末尾还有一道有序矩阵的题目) + +- 堆(优先级队列) + +## 代码 + +```js +/* + * @lc app=leetcode id=378 lang=javascript + * + * [378] Kth Smallest Element in a Sorted Matrix + */ +function notGreaterCount(matrix, target) { + // 等价于在matrix 中搜索mid,搜索的过程中利用有序的性质记录比mid小的元素个数 + + // 我们选择左下角,作为开始元素 + let curRow = 0; + // 多少列 + const COL_COUNT = matrix[0].length; + // 最后一列的索引 + const LAST_COL = COL_COUNT - 1; + let res = 0; + + while (curRow < matrix.length) { + // 比较最后一列的数据和target的大小 + if (matrix[curRow][LAST_COL] < target) { + res += COL_COUNT; + } else { + let i = COL_COUNT - 1; + while (i < COL_COUNT && matrix[curRow][i] > target) { + i--; + } + // 注意这里要加1 + res += i + 1; + } + curRow++; + } + + return res; +} +/** + * @param {number[][]} matrix + * @param {number} k + * @return {number} + */ +var kthSmallest = function(matrix, k) { + if (matrix.length < 1) return null; + let start = matrix[0][0]; + let end = matrix[matrix.length - 1][matrix[0].length - 1]; + while (start < end) { + const mid = start + ((end - start) >> 1); + const count = notGreaterCount(matrix, mid); + if (count < k) start = mid + 1; + else end = mid; + } + // 返回start,mid, end 都一样 + return start; +}; +``` + +## 相关题目 + +- [240.search-a-2-d-matrix-ii](./240.search-a-2-d-matrix-ii.md) diff --git a/problems/380.insert-delete-getrandom-o1.md b/problems/380.insert-delete-getrandom-o1.md new file mode 100644 index 0000000..2688099 --- /dev/null +++ b/problems/380.insert-delete-getrandom-o1.md @@ -0,0 +1,141 @@ +## 题目地址(380. 常数时间插入、删除和获取随机元素) + +https://leetcode-cn.com/problems/insert-delete-getrandom-o1/description/ + +## 题目描述 + +``` +设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构。 + +insert(val):当元素 val 不存在时,向集合中插入该项。 +remove(val):元素 val 存在时,从集合中移除该项。 +getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。 +示例 : + +// 初始化一个空的集合。 +RandomizedSet randomSet = new RandomizedSet(); + +// 向集合中插入 1 。返回 true 表示 1 被成功地插入。 +randomSet.insert(1); + +// 返回 false ,表示集合中不存在 2 。 +randomSet.remove(2); + +// 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。 +randomSet.insert(2); + +// getRandom 应随机返回 1 或 2 。 +randomSet.getRandom(); + +// 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。 +randomSet.remove(1); + +// 2 已在集合中,所以返回 false 。 +randomSet.insert(2); + +// 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。 +randomSet.getRandom(); + +``` + +## 思路 + +这是一个设计题。这道题的核心就是考察基本数据结构和算法的操作以及复杂度。 + +我们来回顾一下基础知识: + +- 数组支持随机访问,其按照索引查询的时间复杂度为$O(1)$,按值查询的时间复杂度为$O(N)$, 而插入和删除的时间复杂度为$O(N)$。 +- 链表不支持随机访问,其查询的时间复杂度为$O(N)$,但是对于插入和删除的复杂度为$O(1)$(不考虑找到选要处理的节点花费的时间)。 +- 对于哈希表,正常情况下其查询复杂度平均为$O(1)$,插入和删除的复杂度为$O(1)$。 + +由于题目要求 getRandom 返回要随机,那么如果单纯使用链表以及哈希表肯定是不行的。而又由于对于插入和删除我们也需要平均复杂度为$O(1)$,因此单纯使用数组也是不行的。我们考虑多种使用数据结构来实现。 + +> 实际上 LeetCode 设计题,几乎没有单纯一个数据结构搞定的,基本都需要多种数据结构结合,这个时候需要你对各种数据结构以及其基本算法的复杂度有着清晰的认知。 + +对于 getRandom 用数组很简单。对于判断是否已经有了存在的元素,我们使用哈希表也很容易做到。因此我们将数组随机访问,以及哈希表$O(1)$按值检索的特性结合起来,即同时使用这两种数据结构。 + +对于删除和插入,我们需要一些技巧。 + +对于插入: + +- 我们直接往 append,并将其插入哈希表即可。 +- 对于删除,我们需要做到 O(1)。删除哈希表很明显可以,但是对于数组,平均时间复杂度为 O(1)。 + +因此如何应付删除的这种性能开销呢? 我们知道对于数据删除,我们的时间复杂度来源于 + +1. `查找到要删除的元素` +2. 以及`重新排列被删除元素后面的元素`。 + +对于 1,我们可以通过哈希表来实现。 key 是插入的数字,value 是数组对应的索引。删除的时候我们根据 key 反查出索引就可以快速找到。 + +对于 2,我们可以通过和数组最后一项进行交换的方式来实现,这样就避免了数据移动。同时数组其他项的索引仍然保持不变,非常好! + +> 相应的我们插入的时候,需要维护哈希表 + +## 关键点解析 + +- 数组 +- 哈希表 +- 数组 + 哈希表 +- 基本算法时间复杂度分析 + +## 代码 + +```python +from random import random + + +class RandomizedSet: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.data = dict() + self.arr = [] + self.n = 0 + + def insert(self, val: int) -> bool: + """ + Inserts a value to the set. Returns true if the set did not already contain the specified element. + """ + if val in self.data: + return False + self.data[val] = self.n + self.arr.append(val) + self.n += 1 + + return True + + def remove(self, val: int) -> bool: + """ + Removes a value from the set. Returns true if the set contained the specified element. + """ + if val not in self.data: + return False + i = self.data[val] + # 更新data + self.data[self.arr[-1]] = i + self.data.pop(val) + # 更新arr + self.arr[i] = self.arr[-1] + # 删除最后一项 + self.arr.pop() + self.n -= 1 + + return True + + def getRandom(self) -> int: + """ + Get a random element from the set. + """ + + return self.arr[int(random() * self.n)] + + +# Your RandomizedSet object will be instantiated and called as such: +# obj = RandomizedSet() +# param_1 = obj.insert(val) +# param_2 = obj.remove(val) +# param_3 = obj.getRandom() +``` diff --git a/problems/39.combination-sum.md b/problems/39.combination-sum.md new file mode 100644 index 0000000..be782f4 --- /dev/null +++ b/problems/39.combination-sum.md @@ -0,0 +1,180 @@ +## 题目地址 +https://leetcode.com/problems/combination-sum/description/ + +## 题目描述 +``` +Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target. + +The same repeated number may be chosen from candidates unlimited number of times. + +Note: + +All numbers (including target) will be positive integers. +The solution set must not contain duplicate combinations. +Example 1: + +Input: candidates = [2,3,6,7], target = 7, +A solution set is: +[ + [7], + [2,2,3] +] +Example 2: + +Input: candidates = [2,3,5], target = 8, +A solution set is: +[ + [2,2,2,2], + [2,3,3], + [3,5] +] + +``` + +## 思路 + +这道题目是求集合,并不是`求极值`,因此动态规划不是特别切合,因此我们需要考虑别的方法。 + +这种题目其实有一个通用的解法,就是回溯法。 +网上也有大神给出了这种回溯法解题的 +[通用写法](https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)),这里的所有的解法使用通用方法解答。 +除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。 + +我们先来看下通用解法的解题思路,我画了一张图: + +![backtrack](../assets/problems/backtrack.png) + +通用写法的具体代码见下方代码区。 + +## 关键点解析 + +- 回溯法 +- backtrack 解题公式 + + +## 代码 + +* 语言支持: Javascript,Python3 + +```js +/* + * @lc app=leetcode id=39 lang=javascript + * + * [39] Combination Sum + * + * https://leetcode.com/problems/combination-sum/description/ + * + * algorithms + * Medium (46.89%) + * Total Accepted: 326.7K + * Total Submissions: 684.2K + * Testcase Example: '[2,3,6,7]\n7' + * + * Given a set of candidate numbers (candidates) (without duplicates) and a + * target number (target), find all unique combinations in candidates where the + * candidate numbers sums to target. + * + * The same repeated number may be chosen from candidates unlimited number of + * times. + * + * Note: + * + * + * All numbers (including target) will be positive integers. + * The solution set must not contain duplicate combinations. + * + * + * Example 1: + * + * + * Input: candidates = [2,3,6,7], target = 7, + * A solution set is: + * [ + * ⁠ [7], + * ⁠ [2,2,3] + * ] + * + * + * Example 2: + * + * + * Input: candidates = [2,3,5], target = 8, + * A solution set is: + * [ + * [2,2,2,2], + * [2,3,3], + * [3,5] + * ] + * + */ + +function backtrack(list, tempList, nums, remain, start) { + if (remain < 0) return; + else if (remain === 0) return list.push([...tempList]); + for (let i = start; i < nums.length; i++) { + tempList.push(nums[i]); + backtrack(list, tempList, nums, remain - nums[i], i); // 数字可以重复使用, i + 1代表不可以重复利用 + tempList.pop(); + } +} +/** + * @param {number[]} candidates + * @param {number} target + * @return {number[][]} + */ +var combinationSum = function(candidates, target) { + const list = []; + backtrack(list, [], candidates.sort((a, b) => a - b), target, 0); + return list; +}; +``` +Python3 Code: +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + """ + 回溯法,层层递减,得到符合条件的路径就加入结果集中,超出则剪枝; + 主要是要注意一些细节,避免重复等; + """ + size = len(candidates) + if size <= 0: + return [] + + # 先排序,便于后面剪枝 + candidates.sort() + + path = [] + res = [] + self._find_path(target, path, res, candidates, 0, size) + + return res + + def _find_path(self, target, path, res, candidates, begin, size): + """沿着路径往下走""" + if target == 0: + res.append(path.copy()) + else: + for i in range(begin, size): + left_num = target - candidates[i] + # 如果剩余值为负数,说明超过了,剪枝 + if left_num < 0: + break + # 否则把当前值加入路径 + path.append(candidates[i]) + # 为避免重复解,我们把比当前值小的参数也从下一次寻找中剔除, + # 因为根据他们得出的解一定在之前就找到过了 + self._find_path(left_num, path, res, candidates, i, size) + # 记得把当前值移出路径,才能进入下一个值的路径 + path.pop() +``` + +## 相关题目 + +- [40.combination-sum-ii](./40.combination-sum-ii.md) +- [46.permutations](./46.permutations.md) +- [47.permutations-ii](./47.permutations-ii.md) +- [78.subsets](./78.subsets.md) +- [90.subsets-ii](./90.subsets-ii.md) +- [113.path-sum-ii](./113.path-sum-ii.md) +- [131.palindrome-partitioning](./131.palindrome-partitioning.md) + diff --git a/problems/4.median-of-two-sorted-array.md b/problems/4.median-of-two-sorted-array.md new file mode 100644 index 0000000..6f8a27e --- /dev/null +++ b/problems/4.median-of-two-sorted-array.md @@ -0,0 +1,246 @@ +## 题目地址 +https://leetcode.com/problems/median-of-two-sorted-arrays/ + +## 题目描述 +``` +There are two sorted arrays nums1 and nums2 of size m and n respectively. + +Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)). + +You may assume nums1 and nums2 cannot be both empty. + +Example 1: + +nums1 = [1, 3] +nums2 = [2] + +The median is 2.0 +Example 2: + +nums1 = [1, 2] +nums2 = [3, 4] + +The median is (2 + 3)/2 = 2.5 +``` + +## 思路 +首先了解一下Median的概念,一个数组中median就是把数组分成左右等分的中位数。 + +如下图: +![median](../assets/problems/4.median-of-two-sorted-array-1.jpg) + +这道题,很容易想到暴力解法,时间复杂度和空间复杂度都是`O(m+n)`, 不符合题中给出`O(log(m+n))`时间复杂度的要求。 +我们可以从简单的解法入手,试了一下,暴力解法也是可以被Leetcode Accept的. 分析中会给出两种解法,暴力求解和二分解法。 + +#### 解法一 - 暴力 (Brute Force) +暴力解主要是要merge两个排序的数组`(A,B)`成一个排序的数组。 + +用两个`pointer(i,j)`,`i` 从数组`A`起始位置开始,即`i=0`开始,`j` 从数组`B`起始位置, 即`j=0`开始. +一一比较 `A[i] 和 B[j]`, +1. 如果`A[i] <= B[j]`, 则把`A[i]` 放入新的数组中,i往后移一位,即 `i+1`. +2. 如果`A[i] > B[j]`, 则把`B[j]` 放入新的数组中,j往后移一位,即 `j+1`. +3. 重复步骤#1 和 #2,直到`i`移到`A`最后,或者`j`移到`B`最后。 +4. 如果`j`移动到`B`数组最后,那么直接把剩下的所有`A`依次放入新的数组中. +5. 如果`i`移动到`A`数组最后,那么直接把剩下的所有`B`依次放入新的数组中. + +Merge的过程如下图。 +![merge two sorted array](../assets/problems/4.median-of-two-sorted-array-2.jpg) + + +*时间复杂度: `O(m+n) - m is length of A, n is length of B`* + +*空间复杂度: `O(m+n)`* + +#### 解法二 - 二分查找 (Binary Search) +由于题中给出的数组都是排好序的,在排好序的数组中查找很容易想到可以用二分查找(Binary Search), 这里对数组长度小的做二分, +保证数组A 和 数组B 做partition 之后 + +`len(Aleft)+len(Bleft)=(m+n+1)/2 - m是数组A的长度, n是数组B的长度` + +对数组A的做partition的位置是区间`[0,m]` + +如图: +![partition A,B](../assets/problems/4.median-of-two-sorted-array-3.png) + +下图给出几种不同情况的例子(注意但左边或者右边没有元素的时候,左边用`INF_MIN`,右边用`INF_MAX`表示左右的元素: +![median examples](../assets/problems/4.median-of-two-sorted-array-5.png) + +下图给出具体做的partition 解题的例子步骤, +![median partition example](../assets/problems/4.median-of-two-sorted-array-4.png) + +*时间复杂度: `O(log(min(m, n)) - m is length of A, n is length of B`* + +*空间复杂度: `O(1)` - 这里没有用额外的空间* + +## 关键点分析 +1. 暴力求解,在线性时间内merge两个排好序的数组成一个数组。 +2. 二分查找,关键点在于 + - 要partition两个排好序的数组成左右两等份,partition需要满足`len(Aleft)+len(Bleft)=(m+n+1)/2 - m是数组A的长度, n是数组B的长度` + + - 并且partition后 A左边最大(`maxLeftA`), A右边最小(`minRightA`), B左边最大(`maxLeftB`), B右边最小(`minRightB`) 满足 +`(maxLeftA <= minRightB && maxLeftB <= minRightA)` + +有了这两个条件,那么median就在这四个数中,根据奇数或者是偶数, +``` +奇数: +median = max(maxLeftA, maxLeftB) +偶数: +median = (max(maxLeftA, maxLeftB) + min(minRightA, minRightB)) / 2 +``` + +## 代码(Java code) +*解法一 - 暴力解法(Brute force)* +```java +class MedianTwoSortedArrayBruteForce { + public double findMedianSortedArrays(int[] nums1, int[] nums2) { + int[] newArr = mergeTwoSortedArray(nums1, nums2); + int n = newArr.length; + if (n % 2 == 0) { + // even + return (double) (newArr[n / 2] + newArr[n / 2 - 1]) / 2; + } else { + // odd + return (double) newArr[n / 2]; + } + } + private int[] mergeTwoSortedArray(int[] nums1, int[] nums2) { + int m = nums1.length; + int n = nums2.length; + int[] res = new int[m + n]; + int i = 0; + int j = 0; + int idx = 0; + while (i < m && j < n) { + if (nums1[i] <= nums2[j]) { + res[idx++] = nums1[i++]; + } else { + res[idx++] = nums2[j++]; + } + } + while (i < m) { + res[idx++] = nums1[i++]; + } + while (j < n) { + res[idx++] = nums2[j++]; + } + return res; + } +} +``` +*解法二 - 二分查找(Binary Search)* +```java +class MedianSortedTwoArrayBinarySearch { + public static double findMedianSortedArraysBinarySearch(int[] nums1, int[] nums2) { + // do binary search for shorter length array, make sure time complexity log(min(m,n)). + if (nums1.length > nums2.length) { + return findMedianSortedArraysBinarySearch(nums2, nums1); + } + int m = nums1.length; + int n = nums2.length; + int lo = 0; + int hi = m; + while (lo <= hi) { + // partition A position i + int i = lo + (hi - lo) / 2; + // partition B position j + int j = (m + n + 1) / 2 - i; + + int maxLeftA = i == 0 ? Integer.MIN_VALUE : nums1[i - 1]; + int minRightA = i == m ? Integer.MAX_VALUE : nums1[i]; + + int maxLeftB = j == 0 ? Integer.MIN_VALUE : nums2[j - 1]; + int minRightB = j == n ? Integer.MAX_VALUE : nums2[j]; + + if (maxLeftA <= minRightB && maxLeftB <= minRightA) { + // total length is even + if ((m + n) % 2 == 0) { + return (double) (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2; + } else { + // total length is odd + return (double) Math.max(maxLeftA, maxLeftB); + } + } else if (maxLeftA > minRightB) { + // binary search left half + hi = i - 1; + } else { + // binary search right half + lo = i + 1; + } + } + return 0.0; + } +} +``` + +## 代码 (javascript code) +*解法一 - 暴力解法(Brute force)* +```js +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number} + */ +var findMedianSortedArrays = function(nums1, nums2) { + // 归并排序 + const merged = [] + let i = 0 + let j = 0 + while(i < nums1.length && j < nums2.length) { + if (nums1[i] < nums2[j]) { + merged.push(nums1[i++]) + } else { + merged.push(nums2[j++]) + } + } + while(i < nums1.length) { + merged.push(nums1[i++]) + } + while(j < nums2.length) { + merged.push(nums2[j++]) + } + + const { length } = merged + return length % 2 === 1 + ? merged[Math.floor(length / 2)] + : (merged[length / 2] + merged[length / 2 - 1]) / 2 +}; +``` + +*解法二 - 二分查找(Binary Search)* +```js +/** + * 二分解法 + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number} + */ +var findMedianSortedArrays = function(nums1, nums2) { + // make sure to do binary search for shorten array + if (nums1.length > nums2.length) { + [nums1, nums2] = [nums2, nums1] + } + const m = nums1.length + const n = nums2.length + let low = 0 + let high = m + while(low <= high) { + const i = low + Math.floor((high - low) / 2) + const j = Math.floor((m + n + 1) / 2) - i + + const maxLeftA = i === 0 ? -Infinity : nums1[i-1] + const minRightA = i === m ? Infinity : nums1[i] + const maxLeftB = j === 0 ? -Infinity : nums2[j-1] + const minRightB = j === n ? Infinity : nums2[j] + + if (maxLeftA <= minRightB && minRightA >= maxLeftB) { + return (m + n) % 2 === 1 + ? Math.max(maxLeftA, maxLeftB) + : (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2 + } else if (maxLeftA > minRightB) { + high = i - 1 + } else { + low = low + 1 + } + } +}; +``` \ No newline at end of file diff --git a/problems/40.combination-sum-ii.md b/problems/40.combination-sum-ii.md new file mode 100644 index 0000000..9a2d3e1 --- /dev/null +++ b/problems/40.combination-sum-ii.md @@ -0,0 +1,182 @@ +## 题目地址 +https://leetcode.com/problems/combination-sum-ii/description/ + +## 题目描述 +``` +Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target. + +Each number in candidates may only be used once in the combination. + +Note: + +All numbers (including target) will be positive integers. +The solution set must not contain duplicate combinations. +Example 1: + +Input: candidates = [10,1,2,7,6,1,5], target = 8, +A solution set is: +[ + [1, 7], + [1, 2, 5], + [2, 6], + [1, 1, 6] +] +Example 2: + +Input: candidates = [2,5,2,1,2], target = 5, +A solution set is: +[ + [1,2,2], + [5] +] + +``` + +## 思路 + +这道题目是求集合,并不是`求极值`,因此动态规划不是特别切合,因此我们需要考虑别的方法。 + +这种题目其实有一个通用的解法,就是回溯法。 +网上也有大神给出了这种回溯法解题的 +[通用写法](https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)),这里的所有的解法使用通用方法解答。 +除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。 + +我们先来看下通用解法的解题思路,我画了一张图: + +![backtrack](../assets/problems/backtrack.png) + +通用写法的具体代码见下方代码区。 + +## 关键点解析 + +- 回溯法 +- backtrack 解题公式 + + +## 代码 + +* 语言支持: Javascript,Python3 + +```js +/* + * @lc app=leetcode id=40 lang=javascript + * + * [40] Combination Sum II + * + * https://leetcode.com/problems/combination-sum-ii/description/ + * + * algorithms + * Medium (40.31%) + * Total Accepted: 212.8K + * Total Submissions: 519K + * Testcase Example: '[10,1,2,7,6,1,5]\n8' + * + * Given a collection of candidate numbers (candidates) and a target number + * (target), find all unique combinations in candidates where the candidate + * numbers sums to target. + * + * Each number in candidates may only be used once in the combination. + * + * Note: + * + * + * All numbers (including target) will be positive integers. + * The solution set must not contain duplicate combinations. + * + * + * Example 1: + * + * + * Input: candidates = [10,1,2,7,6,1,5], target = 8, + * A solution set is: + * [ + * ⁠ [1, 7], + * ⁠ [1, 2, 5], + * ⁠ [2, 6], + * ⁠ [1, 1, 6] + * ] + * + * + * Example 2: + * + * + * Input: candidates = [2,5,2,1,2], target = 5, + * A solution set is: + * [ + * [1,2,2], + * [5] + * ] + * + * + */ +function backtrack(list, tempList, nums, remain, start) { + if (remain < 0) return; + else if (remain === 0) return list.push([...tempList]); + for (let i = start; i < nums.length; i++) { + // 和39.combination-sum 的其中一个区别就是这道题candidates可能有重复 + // 代码表示就是下面这一行 + if(i > start && nums[i] == nums[i-1]) continue; // skip duplicates + tempList.push(nums[i]); + backtrack(list, tempList, nums, remain - nums[i], i + 1); // i + 1代表不可以重复利用, i 代表数字可以重复使用 + tempList.pop(); + } + } +/** + * @param {number[]} candidates + * @param {number} target + * @return {number[][]} + */ +var combinationSum2 = function(candidates, target) { + const list = []; + backtrack(list, [], candidates.sort((a, b) => a - b), target, 0); + return list; +}; +``` +Python3 Code: +```python +class Solution: + def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: + """ + 与39题的区别是不能重用元素,而元素可能有重复; + 不能重用好解决,回溯的index往下一个就行; + 元素可能有重复,就让结果的去重麻烦一些; + """ + size = len(candidates) + if size == 0: + return [] + + # 还是先排序,主要是方便去重 + candidates.sort() + + path = [] + res = [] + self._find_path(candidates, path, res, target, 0, size) + + return res + + def _find_path(self, candidates, path, res, target, begin, size): + if target == 0: + res.append(path.copy()) + else: + for i in range(begin, size): + left_num = target - candidates[i] + if left_num < 0: + break + # 如果存在重复的元素,前一个元素已经遍历了后一个元素与之后元素组合的所有可能 + if i > begin and candidates[i] == candidates[i-1]: + continue + path.append(candidates[i]) + # 开始的 index 往后移了一格 + self._find_path(candidates, path, res, left_num, i+1, size) + path.pop() +``` + +## 相关题目 + +- [39.combination-sum](./39.combination-sum.md) +- [46.permutations](./46.permutations.md) +- [47.permutations-ii](./47.permutations-ii.md) +- [78.subsets](./78.subsets.md) +- [90.subsets-ii](./90.subsets-ii.md) +- [113.path-sum-ii](./113.path-sum-ii.md) +- [131.palindrome-partitioning](./131.palindrome-partitioning.md) diff --git a/problems/416.partition-equal-subset-sum.md b/problems/416.partition-equal-subset-sum.md new file mode 100644 index 0000000..1b90e88 --- /dev/null +++ b/problems/416.partition-equal-subset-sum.md @@ -0,0 +1,86 @@ +## 题目地址 + +https://leetcode.com/problems/partition-equal-subset-sum/description/ + +## 题目描述 + +``` +Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal. + +Note: + +Each of the array element will not exceed 100. +The array size will not exceed 200. + + +Example 1: + +Input: [1, 5, 11, 5] + +Output: true + +Explanation: The array can be partitioned as [1, 5, 5] and [11]. + + +Example 2: + +Input: [1, 2, 3, 5] + +Output: false + +Explanation: The array cannot be partitioned into equal sum subsets. + + +``` + +## 思路 + +题目要求给定一个数组, 问是否能划分为和相等的两个数组。 + +这是一个典型的背包问题,我们可以遍历数组,对于每一个,我们都分两种情况考虑,拿或者不拿。 + +背包问题处理这种离散的可以划分子问题解决的问题很有用。 + +![416.partition-equal-subset-sum-1](../assets/problems/416.partition-equal-subset-sum-1.png) + +如果能够识别出这是一道背包问题,那么就相对容易了。 + +![416.partition-equal-subset-sum-2](../assets/problems/416.partition-equal-subset-sum-2.png) +## 关键点解析 + +- 背包问题 + +## 代码 + +```js +/** + * @param {number[]} nums + * @return {boolean} + */ +var canPartition = function(nums) { + let sum = 0; + for(let num of nums) { + sum += num; + } + + if (sum & 1 === 1) return false; + + const half = sum >> 1; + + let dp = Array(half); + dp[0] = [true, ...Array(nums.length).fill(false)]; + + for(let i = 1; i < nums.length + 1; i++) { + dp[i] = [true, ...Array(half).fill(false)]; + for(let j = 1; j < half + 1; j++) { + if (j >= nums[i - 1]) { + dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]; + } + } + } + + return dp[nums.length][half] +}; + +``` + diff --git a/problems/42.trapping-rain-water.md b/problems/42.trapping-rain-water.md new file mode 100644 index 0000000..baaedce --- /dev/null +++ b/problems/42.trapping-rain-water.md @@ -0,0 +1,111 @@ +## 题目地址 +https://leetcode.com/problems/trapping-rain-water/description/ + +## 题目描述 + + +``` +Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining. + + +The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image! + + +``` + +![42.trapping-rain-water-1](../assets/problems/42.trapping-rain-water-1.png) + +``` +Example: + +Input: [0,1,0,2,1,0,1,3,2,1,2,1] +Output: 6 + +``` + +## 思路 + +这是一道雨水收集的问题, 难度为`hard`. 如图所示,让我们求下过雨之后最多可以积攒多少的水。 + +如果采用暴力求解的话,思路应该是height数组依次求和,然后相加。 + +伪代码: + +```js + +for(let i = 0; i < height.length; i++) { + area += (h[i] - height[i]) * 1; // h为下雨之后的水位 +} + +``` +问题转化为求h,那么h[i]又等于`左右两侧柱子的最大值中的较小值`,即 +`h[i] = Math.min(左边柱子最大值, 右边柱子最大值)` + +如上图那么h为 [0, 1, 1, 2, 2, 2 ,2, 3, 2, 2, 2, 1] + +问题的关键在于求解`左边柱子最大值`和`右边柱子最大值`, +我们其实可以用两个数组来表示`leftMax`, `rightMax`, +以leftMax为例,leftMax[i]代表i的左侧柱子的最大值,因此我们维护两个数组即可。 +## 关键点解析 + +- 建模 `h[i] = Math.min(左边柱子最大值, 右边柱子最大值)`(h为下雨之后的水位) + +## 代码 + +代码支持 JavaScript,Python3: + +JavaScript Code: + +```js + +/* + * @lc app=leetcode id=42 lang=javascript + * + * [42] Trapping Rain Water + * + */ +/** + * @param {number[]} height + * @return {number} + */ +var trap = function(height) { + let max = 0; + let volumn = 0; + const leftMax = []; + const rightMax = []; + + for(let i = 0; i < height.length; i++) { + leftMax[i] = max = Math.max(height[i], max); + } + + max = 0; + + for(let i = height.length - 1; i >= 0; i--) { + rightMax[i] = max = Math.max(height[i], max); + } + + for(let i = 0; i < height.length; i++) { + volumn = volumn + Math.min(leftMax[i], rightMax[i]) - height[i] + } + + return volumn; +}; + +``` + +Python Code: + +```python +class Solution: + def trap(self, heights: List[int]) -> int: + n = len(heights) + l, r = [0] * (n + 1), [0] * (n + 1) + ans = 0 + for i in range(1, len(heights) + 1): + l[i] = max(l[i - 1], heights[i - 1]) + for i in range(len(heights) - 1, 0, -1): + r[i] = max(r[i + 1], heights[i]) + for i in range(len(heights)): + ans += max(0, min(l[i + 1], r[i]) - heights[i]) + return ans +``` diff --git a/problems/437.path-sum-iii.md b/problems/437.path-sum-iii.md new file mode 100644 index 0000000..869ccf0 --- /dev/null +++ b/problems/437.path-sum-iii.md @@ -0,0 +1,159 @@ +## 题目地址 + +https://leetcode.com/problems/path-sum-iii/description/ + +## 题目描述 + +``` +You are given a binary tree in which each node contains an integer value. + +Find the number of paths that sum to a given value. + +The path does not need to start or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes). + +The tree has no more than 1,000 nodes and the values are in the range -1,000,000 to 1,000,000. + +Example: + +root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 + + 10 + / \ + 5 -3 + / \ \ + 3 2 11 + / \ \ +3 -2 1 + +Return 3. The paths that sum to 8 are: + +1. 5 -> 3 +2. 5 -> 2 -> 1 +3. -3 -> 11 +``` + +## 思路 +这道题目是要我们求解出任何一个节点出发到子孙节点的路径中和为指定值。 +注意这里,不一定是从根节点出发,也不一定在叶子节点结束。 + +一种简单的思路就是直接递归解决,空间复杂度O(n) 时间复杂度介于O(nlogn) 和 O(n^2), +具体代码: + +```js +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +// the number of the paths starting from self +function helper(root, sum) { + if (root === null) return 0; + const l = helper(root.left, sum - root.val); + const r = helper(root.right, sum - root.val); + + return l + r + (root.val === sum ? 1 : 0); +} +/** + * @param {TreeNode} root + * @param {number} sum + * @return {number} + */ +var pathSum = function(root, sum) { +// 空间复杂度O(n) 时间复杂度介于O(nlogn) 和 O(n^2) + // tag: dfs tree + if (root === null) return 0; + // the number of the paths starting from self + const self = helper(root, sum); + // we don't know the answer, so we just pass it down + const l = pathSum(root.left, sum); + // we don't know the answer, so we just pass it down + const r = pathSum(root.right, sum); + + return self + l + r; +}; + +``` + + +但是还有一种空间复杂度更加优秀的算法,利用hashmap来避免重复计算,时间复杂度和空间复杂度都是O(n)。 +这种思路是`subarray-sum-equals-k`的升级版本,如果那道题目你可以O(n)解决,这道题目难度就不会很大, +只是将数组换成了二叉树。关于具体的思路可以看[这道题目](./560.subarray-sum-equals-k.md) + + +这里有一个不一样的地方,这里我说明一下,就是为什么要有`hashmap[acc] = hashmap[acc] - 1;`, +原因很简单,就是我们DFS的时候,从底部往上回溯的时候,map的值应该也回溯。如果你对回溯法比较熟悉的话, +应该很容易理解,如果不熟悉可以参考[这道题目](./46.permutations.md), 这道题目就是通过`tempList.pop()`来完成的。 + +另外我画了一个图,相信看完你就明白了。 + +当我们执行到底部的时候: + +![437.path-sum-iii](../assets/problems/437.path-sum-iii-1.jpg) + +接着往上回溯: + +![437.path-sum-iii-2](../assets/problems/437.path-sum-iii-2.jpg) + +很容易看出,我们的hashmap不应该有第一张图的那个记录了,因此需要减去。 + + +具体实现见下方代码区。 + +## 关键点解析 + +- 通过hashmap,以时间换空间 +- 对于这种连续的元素求和问题,有一个共同的思路,可以参考[这道题目](./560.subarray-sum-equals-k.md) + +## 代码 + +* 语言支持:JS + +```js + + +/* + * @lc app=leetcode id=437 lang=javascript + * + * [437] Path Sum III + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +function helper(root, acc, target, hashmap) { + // see also : https://leetcode.com/problems/subarray-sum-equals-k/ + + if (root === null) return 0; + let count = 0; + acc += root.val; + if (acc === target) count++; + if (hashmap[acc - target] !== void 0) { + count += hashmap[acc - target]; + } + if (hashmap[acc] === void 0) { + hashmap[acc] = 1; + } else { + hashmap[acc] += 1; + } + const res = + count + + helper(root.left, acc, target, hashmap) + + helper(root.right, acc, target, hashmap); + + // 这里要注意别忘记了 + hashmap[acc] = hashmap[acc] - 1; + + return res; +} + +var pathSum = function(root, sum) { + // 时间复杂度和空间复杂度都是O(n) + const hashmap = {}; + return helper(root, 0, sum, hashmap); +}; +``` diff --git a/problems/445.add-two-numbers-ii.md b/problems/445.add-two-numbers-ii.md new file mode 100644 index 0000000..3f8e2e7 --- /dev/null +++ b/problems/445.add-two-numbers-ii.md @@ -0,0 +1,259 @@ +## 题目地址 + +https://leetcode.com/problems/add-two-numbers-ii/description/ + +## 题目描述 + +``` +You are given two non-empty linked lists representing two non-negative integers. The most significant digit comes first and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. + +You may assume the two numbers do not contain any leading zero, except the number 0 itself. + +Follow up: +What if you cannot modify the input lists? In other words, reversing the lists is not allowed. + +Example: + +Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) +Output: 7 -> 8 -> 0 -> 7 + +``` + +## 思路 + +由于需要从低位开始加,然后进位。 因此可以采用栈来简化操作。 +依次将两个链表的值分别入栈 stack1 和 stack2,然后相加入栈 stack,进位操作用一个变量 carried 记录即可。 + +最后根据 stack 生成最终的链表即可。 + +> 也可以先将两个链表逆置,然后相加,最后将结果再次逆置。 + +## 关键点解析 + +- 栈的基本操作 +- carried 变量记录进位 +- 循环的终止条件设置成`stack.length > 0` 可以简化操作 +- 注意特殊情况, 比如 1 + 99 = 100 + +## 代码 + +- 语言支持:JS,C++, Python3 + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=445 lang=javascript + * + * [445] Add Two Numbers II + */ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} l1 + * @param {ListNode} l2 + * @return {ListNode} + */ +var addTwoNumbers = function(l1, l2) { + const stack1 = []; + const stack2 = []; + const stack = []; + + let cur1 = l1; + let cur2 = l2; + let curried = 0; + + while (cur1) { + stack1.push(cur1.val); + cur1 = cur1.next; + } + + while (cur2) { + stack2.push(cur2.val); + cur2 = cur2.next; + } + + let a = null; + let b = null; + + while (stack1.length > 0 || stack2.length > 0) { + a = Number(stack1.pop()) || 0; + b = Number(stack2.pop()) || 0; + + stack.push((a + b + curried) % 10); + + if (a + b + curried >= 10) { + curried = 1; + } else { + curried = 0; + } + } + + if (curried === 1) { + stack.push(1); + } + + const dummy = {}; + + let current = dummy; + + while (stack.length > 0) { + current.next = { + val: stack.pop(), + next: null + }; + + current = current.next; + } + + return dummy.next; +}; +``` + +C++ Code: + +```C++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +class Solution { +public: + ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { + auto carry = 0; + auto ret = (ListNode*)nullptr; + auto s1 = vector(); + toStack(l1, s1); + auto s2 = vector(); + toStack(l2, s2); + while (!s1.empty() || !s2.empty() || carry != 0) { + auto v1 = 0; + auto v2 = 0; + if (!s1.empty()) { + v1 = s1.back(); + s1.pop_back(); + } + if (!s2.empty()) { + v2 = s2.back(); + s2.pop_back(); + } + auto v = v1 + v2 + carry; + carry = v / 10; + auto tmp = new ListNode(v % 10); + tmp->next = ret; + ret = tmp; + } + return ret; + } +private: + // 此处若返回而非传入vector,跑完所有测试用例多花8ms + void toStack(const ListNode* l, vector& ret) { + while (l != nullptr) { + ret.push_back(l->val); + l = l->next; + } + } +}; + +// 逆置,相加,再逆置。跑完所有测试用例比第一种解法少花4ms +class Solution { +public: + ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { + auto rl1 = reverseList(l1); + auto rl2 = reverseList(l2); + auto ret = add(rl1, rl2); + return reverseList(ret); + } +private: + ListNode* reverseList(ListNode* head) { + ListNode* prev = NULL; + ListNode* cur = head; + ListNode* next = NULL; + while (cur != NULL) { + next = cur->next; + cur->next = prev; + prev = cur; + cur = next; + } + return prev; + } + + ListNode* add(ListNode* l1, ListNode* l2) { + ListNode* ret = nullptr; + ListNode* cur = nullptr; + int carry = 0; + while (l1 != nullptr || l2 != nullptr || carry != 0) { + carry += (l1 == nullptr ? 0 : l1->val) + (l2 == nullptr ? 0 : l2->val); + auto temp = new ListNode(carry % 10); + carry /= 10; + if (ret == nullptr) { + ret = temp; + cur = ret; + } + else { + cur->next = temp; + cur = cur->next; + } + l1 = l1 == nullptr ? nullptr : l1->next; + l2 = l2 == nullptr ? nullptr : l2->next; + } + return ret; + } +}; +``` + +Python Code: + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: + def listToStack(l: ListNode) -> list: + stack, c = [], l + while c: + stack.append(c.val) + c = c.next + return stack + + # transfer l1 and l2 into stacks + stack1, stack2 = listToStack(l1), listToStack(l2) + + # add stack1 and stack2 + diff = abs(len(stack1) - len(stack2)) + stack1 = ([0]*diff + stack1 if len(stack1) < len(stack2) else stack1) + stack2 = ([0]*diff + stack2 if len(stack2) < len(stack1) else stack2) + stack3 = [x + y for x, y in zip(stack1, stack2)] + + # calculate carry for each item in stack3 and add one to the item before it + carry = 0 + for i, val in enumerate(stack3[::-1]): + index = len(stack3) - i - 1 + carry, stack3[index] = divmod(val + carry, 10) + if carry and index == 0: + stack3 = [1] + stack3 + elif carry: + stack3[index - 1] += 1 + + # transfer stack3 to a linkedList + result = ListNode(0) + c = result + for item in stack3: + c.next = ListNode(item) + c = c.next + + return result.next +``` diff --git a/problems/454.4-sum-ii.md b/problems/454.4-sum-ii.md new file mode 100644 index 0000000..6cadc5a --- /dev/null +++ b/problems/454.4-sum-ii.md @@ -0,0 +1,103 @@ + + +## 题目地址 +https://leetcode.com/problems/4sum-ii/description/ + +## 题目描述 + +``` +Given four lists A, B, C, D of integer values, compute how many tuples (i, j, k, l) there are such that A[i] + B[j] + C[k] + D[l] is zero. + +To make problem a bit easier, all A, B, C, D have same length of N where 0 ≤ N ≤ 500. All integers are in the range of -228 to 228 - 1 and the result is guaranteed to be at most 231 - 1. + +Example: + +Input: +A = [ 1, 2] +B = [-2,-1] +C = [-1, 2] +D = [ 0, 2] + +Output: +2 + +Explanation: +The two tuples are: +1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0 +2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0 +``` +## 思路 + +如果按照常规思路去完成查找需要四层遍历,时间复杂是O(n^4), 显然是行不通的。 +因此我们有必要想一种更加高效的算法。 + +我一个思路就是我们将四个数组分成两组,两两结合。 +然后我们分别计算`两两结合能够算出的和有哪些,以及其对应的个数`。 + +如图: + +![454.4-sum-ii](../assets/problems/454.4-sum-ii.png) + + +这个时候我们得到了两个`hashTable`, 我们只需要进行简单的数学运算就可以得到结果。 + +## 关键点解析 + +- 空间换时间 +- 两两分组,求出两两结合能够得出的可能数,然后合并即可。 + +## 代码 + +语言支持: `JavaScript`,`Python3` + +`JavaScript`: +```js + +/* + * @lc app=leetcode id=454 lang=javascript + * + * [454] 4Sum II + * + * https://leetcode.com/problems/4sum-ii/description/ +/** + * @param {number[]} A + * @param {number[]} B + * @param {number[]} C + * @param {number[]} D + * @return {number} + */ +var fourSumCount = function(A, B, C, D) { + const sumMapper = {}; + let res = 0; + for (let i = 0; i < A.length; i++) { + for (let j = 0; j < B.length; j++) { + sumMapper[A[i] + B[j]] = (sumMapper[A[i] + B[j]] || 0) + 1; + } + } + + for (let i = 0; i < C.length; i++) { + for (let j = 0; j < D.length; j++) { + res += sumMapper[- (C[i] + D[j])] || 0; + } + } + + return res; +}; +``` + +`Python3`: + +```python +class Solution: + def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int: + mapper = {} + res = 0 + for i in A: + for j in B: + mapper[i + j] = mapper.get(i + j, 0) + 1 + + for i in C: + for j in D: + res += mapper.get(-1 * (i + j), 0) + return res + ``` diff --git a/problems/455.AssignCookies.md b/problems/455.AssignCookies.md new file mode 100644 index 0000000..a0e38ca --- /dev/null +++ b/problems/455.AssignCookies.md @@ -0,0 +1,82 @@ +## 题目地址 +https://leetcode-cn.com/problems/assign-cookies + +## 题目描述 +``` +假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 + +注意: + +你可以假设胃口值为正。 +一个小朋友最多只能拥有一块饼干。 + +示例 1: + +输入: [1,2,3], [1,1] + +输出: 1 + +解释: + +你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 +虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 +所以你应该输出1。 + +示例 2: + +输入: [1,2], [1,2,3] + +输出: 2 + +解释: + +你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 +你拥有的饼干数量和尺寸都足以让所有孩子满足。 +所以你应该输出2. +``` + +## 思路 +贪心算法+双指针求解 + +给一个孩子的饼干应当尽量小并且能满足孩子,大的留来满足胃口大的孩子。因为胃口小的孩子最容易得到满足,所以优先满足胃口小的孩子需求。按照从小到大的顺序使用饼干尝试是否可满足某个孩子。 + + +## 关键点 + +将需求因子 g 和 s 分别从小到大进行排序,使用贪心思想,配合双指针,每个饼干只尝试一次,成功则换下一个孩子来尝试。 + + +## 代码 +* 语言支持:JS + +```js +/** + * @param {number[]} g + * @param {number[]} s + * @return {number} + */ +const findContentChildren = function (g, s) { + g = g.sort((a, b) => a - b); + s = s.sort((a, b) => a - b); + let gi = 0; // 胃口值 + let sj = 0; // 饼干尺寸 + let res = 0; + while (gi < g.length && sj < s.length) { + // 当饼干 sj >= 胃口 gi 时,饼干满足胃口,更新满足的孩子数并移动指针 + if (s[sj] >= g[gi]) { + gi++; + sj++; + res++; + } else { + // 当饼干 sj < 胃口 gi 时,饼干不能满足胃口,需要换大的 + sj++; + } + } + return res; +}; +``` + +***复杂度分析*** + +- 时间复杂度:O(NlogN) +- 空间复杂度:O(1) diff --git a/problems/46.permutations.md b/problems/46.permutations.md new file mode 100644 index 0000000..db55f9c --- /dev/null +++ b/problems/46.permutations.md @@ -0,0 +1,177 @@ +## 题目地址 +https://leetcode.com/problems/permutations/description/ + +## 题目描述 +``` +Given a collection of distinct integers, return all possible permutations. + +Example: + +Input: [1,2,3] +Output: +[ + [1,2,3], + [1,3,2], + [2,1,3], + [2,3,1], + [3,1,2], + [3,2,1] +] + +``` + +## 思路 + +这道题目是求集合,并不是`求极值`,因此动态规划不是特别切合,因此我们需要考虑别的方法。 + +这种题目其实有一个通用的解法,就是回溯法。 +网上也有大神给出了这种回溯法解题的 +[通用写法](https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)),这里的所有的解法使用通用方法解答。 +除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。 + +我们先来看下通用解法的解题思路,我画了一张图: + +![backtrack](../assets/problems/backtrack.png) + +通用写法的具体代码见下方代码区。 + +## 关键点解析 + +- 回溯法 +- backtrack 解题公式 + +## 代码 + +* 语言支持: Javascript, Python3 + +Javascript Code: + +```js +/* + * @lc app=leetcode id=46 lang=javascript + * + * [46] Permutations + * + * https://leetcode.com/problems/permutations/description/ + * + * algorithms + * Medium (53.60%) + * Total Accepted: 344.6K + * Total Submissions: 642.9K + * Testcase Example: '[1,2,3]' + * + * Given a collection of distinct integers, return all possible permutations. + * + * Example: + * + * + * Input: [1,2,3] + * Output: + * [ + * ⁠ [1,2,3], + * ⁠ [1,3,2], + * ⁠ [2,1,3], + * ⁠ [2,3,1], + * ⁠ [3,1,2], + * ⁠ [3,2,1] + * ] + * + * + */ +function backtrack(list, tempList, nums) { + if (tempList.length === nums.length) return list.push([...tempList]); + for(let i = 0; i < nums.length; i++) { + if (tempList.includes(nums[i])) continue; + tempList.push(nums[i]); + backtrack(list, tempList, nums); + tempList.pop(); + } +} +/** + * @param {number[]} nums + * @return {number[][]} + */ +var permute = function(nums) { + const list = []; + backtrack(list, [], nums) + return list +}; +``` +Python3 Code: +```Python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + """itertools库内置了这个函数""" + return itertools.permutations(nums) + + def permute2(self, nums: List[int]) -> List[List[int]]: + """自己写回溯法""" + res = [] + def _backtrace(nums, pre_list): + if len(nums) <= 0: + res.append(pre_list) + else: + for i in nums: + # 注意copy一份新的调用,否则无法正常循环 + p_list = pre_list.copy() + p_list.append(i) + left_nums = nums.copy() + left_nums.remove(i) + _backtrace(left_nums, p_list) + _backtrace(nums, []) + return res +``` + +Python Code: + +```Python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + """itertools库内置了这个函数""" + import itertools + return itertools.permutations(nums) + + def permute2(self, nums: List[int]) -> List[List[int]]: + """自己写回溯法""" + res = [] + def _backtrace(nums, pre_list): + if len(nums) <= 0: + res.append(pre_list) + else: + for i in nums: + # 注意copy一份新的调用,否则无法正常循环 + p_list = pre_list.copy() + p_list.append(i) + left_nums = nums.copy() + left_nums.remove(i) + _backtrace(left_nums, p_list) + _backtrace(nums, []) + return res + + def permute3(self, nums: List[int]) -> List[List[int]]: + """回溯的另一种写法""" + res = [] + length = len(nums) + def _backtrack(start=0): + if start == length: + # nums[:] 返回 nums 的一个副本,指向新的引用,这样后续的操作不会影响已经已知解 + res.append(nums[:]) + for i in range(start, length): + nums[start], nums[i] = nums[i], nums[start] + _backtrack(start+1) + nums[start], nums[i] = nums[i], nums[start] + _backtrack() + return res +``` + +## 相关题目 + +- [31.next-permutation](./31.next-permutation.md) +- [39.combination-sum](./39.combination-sum.md) +- [40.combination-sum-ii](./40.combination-sum-ii.md) +- [47.permutations-ii](./47.permutations-ii.md) +- [60.permutation-sequence](./60.permutation-sequence.md) +- [78.subsets](./78.subsets.md) +- [90.subsets-ii](./90.subsets-ii.md) +- [113.path-sum-ii](./113.path-sum-ii.md) +- [131.palindrome-partitioning](./131.palindrome-partitioning.md) diff --git a/problems/460.lfu-cache.md b/problems/460.lfu-cache.md new file mode 100644 index 0000000..d9e9f30 --- /dev/null +++ b/problems/460.lfu-cache.md @@ -0,0 +1,242 @@ +## 题目地址 +https://leetcode.com/problems/lfu-cache/ + +## 题目描述 + +``` +Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get and put. + +get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. +put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted. + +Follow up: +Could you do both operations in O(1) time complexity? + +Example: + +LFUCache cache = new LFUCache( 2 /* capacity */ ); + +cache.put(1, 1); +cache.put(2, 2); +cache.get(1); // returns 1 +cache.put(3, 3); // evicts key 2 +cache.get(2); // returns -1 (not found) +cache.get(3); // returns 3. +cache.put(4, 4); // evicts key 1. +cache.get(1); // returns -1 (not found) +cache.get(3); // returns 3 +cache.get(4); // returns 4 +``` + +## 思路 + +`本题已被收录到我的新书中,敬请期待~` + +[LFU(Least frequently used)](https://www.wikiwand.com/en/Least_frequently_used) 但内存容量满的情况下,有新的数据进来,需要更多空间的时候,就需要删除被访问频率最少的元素。 + +举个例子,比如说 cache 容量是 3,按顺序依次放入 `1,2,1,2,1,3`, cache 已存满 3 个元素 (1,2,3), 这时如果想放入一个新的元素 4 的时候,就需要腾出一个元素空间。 +用 LFU,这里就淘汰 3, 因为 3 的次数只出现依次, 1 和 2 出现的次数都比 3 多。 + +题中 `get` 和 `put` 都是 `O(1)`的时间复杂度,那么删除和增加都是`O(1)`,可以想到用双链表,和`HashMap`,用一个`HashMap, nodeMap,` 保存当前`key`,和 `node{key, value, frequent} `的映射。 +这样`get(key)`的操作就是`O(1)`. 如果要删除一个元素,那么就需要另一个`HashMap,freqMap,`保存元素出现次数`(frequent)`和双链表`(DoublyLinkedlist)` 映射, +这里双链表存的是 frequent 相同的元素。每次`get`或`put`的时候,`frequent+1`,然后把`node`插入到双链表的`head node, head.next=node` +每次删除`freqent`最小的双链表的`tail node, tail.prev`。 + +用给的例子举例说明: + ``` + 1. put(1, 1), + - 首先查找 nodeMap 中有没有 key=1 对应的 value, + 没有就新建 node(key, value, freq) -> node1(1, 1, 1), 插入 nodeMap,{[1, node1]} + - 查找 freqMap 中有没有 freq=1 对应的 value, + 没有就新建 doublylinkedlist(head, tail), 把 node1 插入 doublylinkedlist head->next = node1. + 如下图, + ``` +![460.lfu-cache-1](../assets/problems/460.lfu-cache-1.jpg) + ``` + 2. put(2, 2), + - 首先查找 nodeMap 中有没有 key=2 对应的 value, + 没有就新建 node(key, value, freq) -> node2(2, 2, 1), 插入 nodeMap,{[1, node1], [2, node2]} + - 查找 freqMap 中有没有 freq=1 对应的 value, + 没有就新建 doublylinkedlist(head, tail), 把 node2 插入 doublylinkedlist head->next = node2. + 如下图, + ``` +![460.lfu-cache-2](../assets/problems/460.lfu-cache-2.jpg) + ``` + 3. get(1), + - 首先查找 nodeMap 中有没有 key=1 对应的 value,nodeMap:{[1, node1], [2, node2]}, + 找到 node1,把 node1 freq+1 -> node1(1,1,2) + - 更新 freqMap,删除 freq=1,node1 + - 更新 freqMap,插入 freq=2,node1 + 如下图, + ``` +![460.lfu-cache-3](../assets/problems/460.lfu-cache-3.jpg) + ``` + 4. put(3, 3), + - 判断 cache 的 capacity,已满,需要淘汰使用次数最少的元素,找到最小的 freq=1,删除双链表 tail node.prev + 如果 tailnode.prev != null, 删除。然后从 nodeMap 中删除对应的 key。 + - 首先查找 nodeMap 中有没有 key=3 对应的 value, + 没有就新建 node(key, value, freq) -> node3(3, 3, 1), 插入 nodeMap,{[1, node1], [3, node3]} + - 查找 freqMap 中有没有 freq=1 对应的 value, + 没有就新建 doublylinkedlist(head, tail), 把 node3 插入 doublylinkedlist head->next = node3. + 如下图, + ``` +![460.lfu-cache-4](../assets/problems/460.lfu-cache-4.jpg) + ``` + 5. get(2) + - 查找 nodeMap,如果没有对应的 key 的 value,返回 -1。 + + 6. get(3) + - 首先查找 nodeMap 中有没有 key=3 对应的 value,nodeMap:{[1, node1], [3, node3]}, + 找到 node3,把 node3 freq+1 -> node3(3,3,2) + - 更新 freqMap,删除 freq=1,node3 + - 更新 freqMap,插入 freq=2,node3 + 如下图, + ``` +![460.lfu-cache-5](../assets/problems/460.lfu-cache-5.jpg) + ``` + 7. put(4, 4), + - 判断 cache 的 capacity,已满,需要淘汰使用次数最少的元素,找到最小的 freq=1,删除双链表 tail node.prev + 如果 tailnode.prev != null, 删除。然后从 nodeMap 中删除对应的 key。 + - 首先查找 nodeMap 中有没有 key=4 对应的 value, + 没有就新建 node(key, value, freq) -> node4(4, 4, 1), 插入 nodeMap,{[4, node4], [3, node3]} + - 查找 freqMap 中有没有 freq=1 对应的 value, + 没有就新建 doublylinkedlist(head, tail), 把 node4 插入 doublylinkedlist head->next = node4. + 如下图, + ``` +![460.lfu-cache-6](../assets/problems/460.lfu-cache-6.jpg) + ``` + 8. get(1) + - 查找 nodeMap,如果没有对应的 key 的 value,返回 -1。 + + 9. get(3) + - 首先查找 nodeMap 中有没有 key=3 对应的 value,nodeMap:{[4, node4], [3, node3]}, + 找到 node3,把 node3 freq+1 -> node3(3,3,3) + - 更新 freqMap,删除 freq=2,node3 + - 更新 freqMap,插入 freq=3,node3 + 如下图, + ``` +![460.lfu-cache-7](../assets/problems/460.lfu-cache-7.jpg) + ``` + 10. get(4) + - 首先查找 nodeMap 中有没有 key=4 对应的 value,nodeMap:{[4, node4], [3, node3]}, + 找到 node4,把 node4 freq+1 -> node4(4,4,2) + - 更新 freqMap,删除 freq=1,node4 + - 更新 freqMap,插入 freq=2,node4 + 如下图, + ``` +![460.lfu-cache-8](../assets/problems/460.lfu-cache-8.jpg) + +## 关键点分析 +用两个`Map`分别保存 `nodeMap {key, node}` 和 `freqMap{frequent, DoublyLinkedList}`。 +实现`get` 和 `put`操作都是`O(1)`的时间复杂度。 + +可以用 Java 自带的一些数据结构,比如 HashLinkedHashSet,这样就不需要自己自建 Node,DoublelyLinkedList。 +可以很大程度的缩减代码量。 + +## 代码(Java code) +```java +public class LC460LFUCache { + class Node { + int key, val, freq; + Node prev, next; + + Node(int key, int val) { + this.key = key; + this.val = val; + freq = 1; + } + } + + class DoubleLinkedList { + private Node head; + private Node tail; + private int size; + + DoubleLinkedList() { + head = new Node(0, 0); + tail = new Node(0, 0); + head.next = tail; + tail.prev = head; + } + + void add(Node node) { + head.next.prev = node; + node.next = head.next; + node.prev = head; + head.next = node; + size++; + } + + void remove(Node node) { + node.prev.next = node.next; + node.next.prev = node.prev; + size--; + } + + // always remove last node if last node exists + Node removeLast() { + if (size > 0) { + Node node = tail.prev; + remove(node); + return node; + } else return null; + } + } + + // cache capacity + private int capacity; + // min frequent + private int minFreq; + Map nodeMap; + Map freqMap; + public LC460LFUCache(int capacity) { + this.minFreq = 0; + this.capacity = capacity; + nodeMap = new HashMap<>(); + freqMap = new HashMap<>(); + } + + public int get(int key) { + Node node = nodeMap.get(key); + if (node == null) return -1; + update(node); + return node.val; + } + + public void put(int key, int value) { + if (capacity == 0) return; + Node node; + if (nodeMap.containsKey(key)) { + node = nodeMap.get(key); + node.val = value; + update(node); + } else { + node = new Node(key, value); + nodeMap.put(key, node); + if (nodeMap.size() == capacity) { + DoubleLinkedList lastList = freqMap.get(minFreq); + nodeMap.remove(lastList.removeLast().key); + } + minFreq = 1; + DoubleLinkedList newList = freqMap.getOrDefault(node.freq, new DoubleLinkedList()); + newList.add(node); + freqMap.put(node.freq, newList); + } + } + + private void update(Node node) { + DoubleLinkedList oldList = freqMap.get(node.freq); + oldList.remove(node); + if (node.freq == minFreq && oldList.size == 0) minFreq++; + node.freq++; + DoubleLinkedList newList = freqMap.getOrDefault(node.freq, new DoubleLinkedList()); + newList.add(node); + freqMap.put(node.freq, newList); + } + } +``` + +## 参考(References) +1. [LFU(Least frequently used) Cache](https://www.wikiwand.com/en/Least_frequently_used) +2. [Leetcode discussion mylzsd](https://leetcode.com/problems/lfu-cache/discuss/94547/Java-O(1)-Solution-Using-Two-HashMap-and-One-DoubleLinkedList) +3. [Leetcode discussion aaaeeeo](https://leetcode.com/problems/lfu-cache/discuss/94547/Java-O(1)-Solution-Using-Two-HashMap-and-One-DoubleLinkedList) diff --git a/problems/47.permutations-ii.md b/problems/47.permutations-ii.md new file mode 100644 index 0000000..0ab000f --- /dev/null +++ b/problems/47.permutations-ii.md @@ -0,0 +1,140 @@ +## 题目地址 +https://leetcode.com/problems/permutations-ii/description/ + +## 题目描述 +``` +Given a collection of numbers that might contain duplicates, return all possible unique permutations. + +Example: + +Input: [1,1,2] +Output: +[ + [1,1,2], + [1,2,1], + [2,1,1] +] + +``` + +## 思路 + +这道题目是求集合,并不是`求极值`,因此动态规划不是特别切合,因此我们需要考虑别的方法。 + +这种题目其实有一个通用的解法,就是回溯法。 +网上也有大神给出了这种回溯法解题的 +[通用写法](https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)),这里的所有的解法使用通用方法解答。 +除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。 + +我们先来看下通用解法的解题思路,我画了一张图: + +![backtrack](../assets/problems/backtrack.png) + +通用写法的具体代码见下方代码区。 + +## 关键点解析 + +- 回溯法 +- backtrack 解题公式 + + +## 代码 + +* 语言支持: Javascript,Python3 + +```js +/* + * @lc app=leetcode id=47 lang=javascript + * + * [47] Permutations II + * + * https://leetcode.com/problems/permutations-ii/description/ + * + * algorithms + * Medium (39.29%) + * Total Accepted: 234.1K + * Total Submissions: 586.2K + * Testcase Example: '[1,1,2]' + * + * Given a collection of numbers that might contain duplicates, return all + * possible unique permutations. + * + * Example: + * + * + * Input: [1,1,2] + * Output: + * [ + * ⁠ [1,1,2], + * ⁠ [1,2,1], + * ⁠ [2,1,1] + * ] + * + * + */ +function backtrack(list, nums, tempList, visited) { + if (tempList.length === nums.length) return list.push([...tempList]); + for (let i = 0; i < nums.length; i++) { + // 和46.permutations的区别是这道题的nums是可以重复的 + // 我们需要过滤这种情况 + if (visited[i]) continue; // 不能用tempList.includes(nums[i])了,因为有重复 + // visited[i - 1] 这个判断容易忽略 + if (i > 0 && nums[i] === nums[i - 1] && visited[i - 1]) continue; + + visited[i] = true; + tempList.push(nums[i]); + backtrack(list, nums, tempList, visited); + visited[i] = false; + tempList.pop(); + } +} +/** + * @param {number[]} nums + * @return {number[][]} + */ +var permuteUnique = function(nums) { + const list = []; + backtrack(list, nums.sort((a, b) => a - b), [], []); + return list; +}; +``` +Python3 code: +```Python +class Solution: + def permuteUnique(self, nums: List[int]) -> List[List[int]]: + """与46题一样,当然也可以直接调用itertools的函数,然后去重""" + return list(set(itertools.permutations(nums))) + + def permuteUnique(self, nums: List[int]) -> List[List[int]]: + """自己写回溯法,与46题相比,需要去重""" + # 排序是为了去重 + nums.sort() + res = [] + def _backtrace(nums, pre_list): + if len(nums) <= 0: + res.append(pre_list) + else: + for i in range(len(nums)): + # 如果是同样的数字,则之前一定已经生成了对应可能 + if i > 0 and nums[i] == nums[i-1]: + continue + p_list = pre_list.copy() + p_list.append(nums[i]) + left_nums = nums.copy() + left_nums.pop(i) + _backtrace(left_nums, p_list) + _backtrace(nums, []) + return res +``` + +## 相关题目 + +- [31.next-permutation](./31.next-permutation.md) +- [39.combination-sum](./39.combination-sum.md) +- [40.combination-sum-ii](./40.combination-sum-ii.md) +- [46.permutations](./46.permutations.md) +- [60.permutation-sequence](./60.permutation-sequence.md)(TODO) +- [78.subsets](./78.subsets.md) +- [90.subsets-ii](./90.subsets-ii.md) +- [113.path-sum-ii](./113.path-sum-ii.md) +- [131.palindrome-partitioning](./131.palindrome-partitioning.md) diff --git a/problems/472.concatenated-words.md b/problems/472.concatenated-words.md new file mode 100644 index 0000000..409948d --- /dev/null +++ b/problems/472.concatenated-words.md @@ -0,0 +1,121 @@ +## 题目地址(472. 连接词) + +https://leetcode-cn.com/problems/concatenated-words/ + +## 题目描述 + +``` +给定一个不含重复单词的列表,编写一个程序,返回给定单词列表中所有的连接词。 + +连接词的定义为:一个字符串完全是由至少两个给定数组中的单词组成的。 + +示例: + +输入: ["cat","cats","catsdogcats","dog","dogcatsdog","hippopotamuses","rat","ratcatdogcat"] + +输出: ["catsdogcats","dogcatsdog","ratcatdogcat"] + +解释: "catsdogcats"由"cats", "dog" 和 "cats"组成; + "dogcatsdog"由"dog", "cats"和"dog"组成; + "ratcatdogcat"由"rat", "cat", "dog"和"cat"组成。 +说明: + +给定数组的元素总数不超过 10000。 +给定数组中元素的长度总和不超过 600000。 +所有输入字符串只包含小写字母。 +不需要考虑答案输出的顺序。 +``` + +## 思路 + +本题我的思路是直接使用前缀树来解决。**标准的前缀树模板**我在之前的题解中提到了,感兴趣的可以到下方的相关题目中查看。 + +这道题这里我们不需要 search,我们的做法是: + +- 先进行一次遍历,将 words 全部插入(insert)到前缀树中。 +- 然后再进行一次遍历,查找每一个单词有几个单词表中的单词组成 +- 如果大于 2,则将其加入到 res 中 +- 最后返回 res 即可 + +我们构造的前缀树大概是这样的: + +![472.concatenated-words.png](http://ww1.sinaimg.cn/large/e9f490c8ly1gbkcox4r06j21eb15fgq8.jpg) + +问题的关键在于第二步中的**查找每一个单词有几个单词表中的单词组成**。 其实如果你了解前缀树的话应该不难写出来。 比如查找 catsdogcats: + +- 我们先从 c 到 a 到 t,发现 t 是单词结尾,我们数量 + 1 +- 然后将剩下的部分“sdogcats”重新执行上述过程。 +- s 发现找不到,我们返回 0 +- 因此最终结果是 1 + +很明显这个逻辑是错误的,正确的划分应该是: + +- 我们先从 c 到 a 到 t,再到 s,此时数量 + 1 +- 然后将剩下的“dogcats”重复上述过程 +- dog 找到了,数量 + 1 +- 最后将 cats 加入。也找到了,数量再加 1 + +由于我们并不知道 cat 这里断开,结果更大?还是 cats 这里断开结果更大?因此我们的做法是将其全部递归求出,然后取出最大值即可。如果我们直接这样递归的话,可能会超时,卡在最后一个测试用例上。一个简单的方式是记忆化递归,从而避免重复计算,经测试这种方法能够通过。 + +## 代码 + +代码支持:Python3 + +Python3 Code: + +```python +class Trie: + + def __init__(self): + self.Trie = {} + self.visited = {} + + def insert(self, word): + curr = self.Trie + for w in word: + if w not in curr: + curr[w] = {} + curr = curr[w] + curr['#'] = 1 + + def cntWords(self, word): + if not word: + return 0 + if word in self.visited: + return self.visited[word] + curr = self.Trie + res = float('-inf') + + for i, w in enumerate(word): + if w not in curr: + return res + curr = curr[w] + if '#' in curr: + res = max(res, 1 + self.cntWords(word[i + 1:])) + self.visited[word] = res + return res + + +class Solution: + def findAllConcatenatedWordsInADict(self, words: List[str]) -> List[str]: + self.trie = Trie() + res = [] + + for word in words: + self.trie.insert(word) + for word in words: + if self.trie.cntWords(word) >= 2: + res.append(word) + return res +``` + +## 关键点分析 + +- 前缀树 + +## 相关题目 + +- [0208.implement-trie-prefix-tree](https://github.com/azl397985856/leetcode/blob/b8e8fa5f0554926efa9039495b25ed7fc158372a/problems/208.implement-trie-prefix-tree.md) +- [0211.add-and-search-word-data-structure-design](https://github.com/azl397985856/leetcode/blob/b0b69f8f11dace3a9040b54532105d42e88e6599/problems/211.add-and-search-word-data-structure-design.md) +- [0212.word-search-ii](https://github.com/azl397985856/leetcode/blob/b0b69f8f11dace3a9040b54532105d42e88e6599/problems/212.word-search-ii.md) +- [0820.short-encoding-of-words](https://github.com/azl397985856/leetcode/blob/master/problems/820.short-encoding-of-words.md) diff --git a/problems/474.ones-and-zeros-en.md b/problems/474.ones-and-zeros-en.md new file mode 100644 index 0000000..f6e1da7 --- /dev/null +++ b/problems/474.ones-and-zeros-en.md @@ -0,0 +1,299 @@ +## Problem +https://leetcode.com/problems/ones-and-zeroes/ + +## Problem Description +``` +In the computer world, use restricted resource you have to generate maximum benefit is what we always want to pursue. + +For now, suppose you are a dominator of m 0s and n 1s respectively. On the other hand, there is an array with strings consisting of only 0s and 1s. + +Now your task is to find the maximum number of strings that you can form with given m 0s and n 1s. Each 0 and 1 can be used at most once. + +Note: + +The given numbers of 0s and 1s will both not exceed 100 +The size of given string array won't exceed 600. + +Example 1: + +Input: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3 +Output: 4 + +Explanation: This are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are “10,”0001”,”1”,”0” + +Example 2: + +Input: Array = {"10", "0", "1"}, m = 1, n = 1 +Output: 2 + +Explanation: You could form "10", but then you'd have nothing left. Better form "0" and "1". +``` + + +## Solution + +When see the requirement of returning maximum number, length etc, and not require to list all possible value. Usually it can +be solved by DP. + +This problem we can see is a `0-1 backpack` issue, either take current string or not take current string. + +And during interview, DP problem is hard to come up with immediately, recommend starting from Brute force, then optimize the solution step by step, until interviewer is happy, :-) + +Below give 4 solutions based on complexities analysis. + +#### Solution #1 - Brute Force (Recursively) + +Brute force solution is to calculate all possible subsets. Then count `0s` and `1s` for each subset, use global variable `max` to keep track of maximum number. +if `count(0) <= m && count(1) <= n;`, then current string can be taken into counts. + +for `strs` length `len`, total subsets are `2^len`, Time Complexity is too high in this solution. + +#### Complexity Analysis +- *Time Complexity:* `O(2^len * s) - len is Strs length, s is the average string length ` +- *Space Complexity:* `O(1)` + +#### Solution #2 - Memorization + Recursive +In Solution #1, brute force, we used recursive to calculate all subsets, in reality, many cases are duplicate, so that we can use +memorization to record those subsets which realdy been calculated, avoid dup calculation. Use a memo array, if already calculated, +then return memo value, otherwise, set the max value into memo. + +`memo[i][j][k] - maximum number of strings can be formed by j 0s and k 1s in range [0, i] strings` + +`helper(strs, i, j, k, memo)` recursively: +1. if `memo[i][j][k] != 0`, meaning already checked for j 0s and k 1s case, return value. +2. if not checked, then check condition `count0 <= j && count1 <= k`, + a. if true,take current strings `strs[i]`, so` 0s -> j-count0`, and `1s -> k-count1`, + check next string `helper(strs, i+1, j-count0, k-count1, memo)` +3. not take current string `strs[i]`, check next string `helper(strs, i+1, j, k, memo)` +4. save max value into`memo[i][j][k]` +5. recursively + +#### Complexity Analysis +- *Time Complexity:* `O(l * m * n) - l is length of strs, m is number of 0s, n is number of 1s` +- *Space Complexity:* `O(l * m * n) - memo 3D Array` + +#### Solution #3 - 3D DP +In Solution #2, used memorization + recursive, this Solution use 3D DP to represent iterative way + +`dp[i][j][k] - the maximum number of strings can be formed using j 0s and k 1s in range [0, i] strings` + +DP Formula: +`dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - count0][k - count1])` - `count0 - number of 0s in strs[i]` and `count1 - number of 1s in strs[i]` + + compare `j` and `count0`, `k` and `count1`, based on taking current string or not, DP formula as below: +- `j >= count0 && k >= count1`, + + `dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - count0][k - count1] + 1)` + +- not meet condition, not take current string + + `dp[i][j][k] = dp[i - 1][j][k]` + +`dp[l][m][n]` is result. + +#### Complexity Analysis +- *Time Complexity:* `O(l * m * n) - l is strs length, m is number of 0s, n is number of 1s` +- *Space Complexity:* `O(l * m * n) - dp 3D array` + +#### Solution #4 - 2D DP + +In Solution #3, we kept track all state value, but we only need previous state, so we can reduce 3 dimention to 2 dimention array, +here we use `dp[2][m][n]`, rotate track previous and current values. Further observation, we notice that first row (track previous state), +we don't need the whole row values, we only care about 2 position value: `dp[i - 1][j][k]` and `dp[i - 1][j - count0][k - count1]`. +so it can be reduced to 2D array. `dp[m][n]`. + +2D DP definition: + +`dp[m+1][n+1] - maximum counts, m is number of 0, n is number of 1` + +DP formula: + +`dp[i][j] = max(dp[i][j], dp[i - count0][j - count1] + 1)` + +For example: + +![ones and zeros 2d dp](../assets/problems/474.ones-and-zeros-2d-dp.png) + +#### +- *Time Complexity:* `O(l * m * n) - l is strs length,m is number of 0,n number of 1` +- *Space Complexity:* `O(m * n) - dp 2D array` + +## Key Points Analysis + +## Code (`Java/Python3`) +#### Solution #1 - Recursive (TLE) +*Java code* +```java +class OnesAndZerosBFRecursive { + public int findMaxForm2(String[] strs, int m, int n) { + return helper(strs, 0, m, n); + } + private int helper(String[] strs, int idx, int j, int k) { + if (idx == strs.length) return 0; + // count current idx string number of zeros and ones + int[] counts = countZeroOnes(strs[idx]); + // if j >= count0 && k >= count1, take current index string + int takeCurrStr = j - counts[0] >= 0 && k - counts[1] >= 0 + ? 1 + helper(strs, idx + 1, j - counts[0], k - counts[1]) + : -1; + // don't take current index string strs[idx], continue next string + int notTakeCurrStr = helper(strs, idx + 1, j, k); + return Math.max(takeCurrStr, notTakeCurrStr); + } + private int[] countZeroOnes(String s) { + int[] res = new int[2]; + for (char ch : s.toCharArray()) { + res[ch - '0']++; + } + return res; + } +} +``` + +*Python3 code* +```python +class Solution: + def findMaxForm(self, strs: List[str], m: int, n: int) -> int: + return self.helper(strs, m, n, 0) + + def helper(self, strs, m, n, idx): + if idx == len(strs): + return 0 + take_curr_str = -1 + count0, count1 = strs[idx].count('0'), strs[idx].count('1') + if m >= count0 and n >= count1: + take_curr_str = max(take_curr_str, self.helper(strs, m - count0, n - count1, idx + 1) + 1) + not_take_curr_str = self.helper(strs, m, n, idx + 1) + return max(take_curr_str, not_take_curr_str) + +``` + +#### Solution #2 - Memorization + Recursive +*Java code* +```java +class OnesAndZerosMemoRecur { + public int findMaxForm4(String[] strs, int m, int n) { + return helper(strs, 0, m, n, new int[strs.length][m + 1][n + 1]); + } + private int helper(String[] strs, int idx, int j, int k, int[][][] memo) { + if (idx == strs.length) return 0; + // check if already calculated, return value + if (memo[idx][j][k] != 0) { + return memo[idx][j][k]; + } + int[] counts = countZeroOnes(strs[idx]); + // if satisfy condition, take current string, strs[idx], update count0 and count1 + int takeCurrStr = j - counts[0] >= 0 && k - counts[1] >= 0 + ? 1 + helper(strs, idx + 1, j - counts[0], k - counts[1], memo) + : -1; + // not take current string + int notTakeCurrStr = helper(strs, idx + 1, j, k, memo); + // always keep track the max value into memory + memo[idx][j][k] = Math.max(takeCurrStr, notTakeCurrStr); + return memo[idx][j][k]; + } + private int[] countZeroOnes(String s) { + int[] res = new int[2]; + for (char ch : s.toCharArray()) { + res[ch - '0']++; + } + return res; + } +} +``` + +*Python3 code* - (TLE) +```python +class Solution: + def findMaxForm(self, strs: List[str], m: int, n: int) -> int: + memo = {k:[[0]*(n+1) for _ in range(m+1)] for k in range(len(strs)+1)} + return self.helper(strs, 0, m, n, memo) + + def helper(self, strs, idx, m, n, memo): + if idx == len(strs): + return 0 + if memo[idx][m][n] != 0: + return memo[idx][m][n] + take_curr_str = -1 + count0, count1 = strs[idx].count('0'), strs[idx].count('1') + if m >= count0 and n >= count1: + take_curr_str = max(take_curr_str, self.helper(strs, idx + 1, m - count0, n - count1, memo) + 1) + not_take_curr_str = self.helper(strs, idx + 1, m, n, memo) + memo[idx][m][n] = max(take_curr_str, not_take_curr_str) + return memo[idx][m][n] +``` + + +#### Solution #3 - 3D DP +*Java code* +```java +class OnesAndZeros3DDP { + public int findMaxForm(String[] strs, int m, int n) { + int l = strs.length; + int [][][] d = new int[l + 1][m + 1][n + 1]; + for (int i = 0; i <= l; i ++){ + int [] nums = new int[]{0,0}; + if (i > 0){ + nums = countZeroOnes(strs[i - 1]); + } + for (int j = 0; j <= m; j ++){ + for (int k = 0; k <= n; k ++){ + if (i == 0) { + d[i][j][k] = 0; + } else if (j >= nums[0] && k >= nums[1]){ + d[i][j][k] = Math.max(d[i - 1][j][k], d[i - 1][j - nums[0]][k - nums[1]] + 1); + } else { + d[i][j][k] = d[i - 1][j][k]; + } + } + } + } + return d[l][m][n]; + } +} +``` +#### Solution #4 - 2D DP +*Java code* +```java +class OnesAndZeros2DDP { + public int findMaxForm(String[] strs, int m, int n) { + int[][] dp = new int[m + 1][n + 1]; + for (String s : strs) { + int[] counts = countZeroOnes(s); + for (int i = m; i >= counts[0]; i--) { + for (int j = n; j >= counts[1]; j--) { + dp[i][j] = Math.max(1 + dp[i - counts[0]][j - counts[1]], dp[i][j]); + } + } + } + return dp[m][n]; + } + private int[] countZeroOnes(String s) { + int[] res = new int[2]; + for (char ch : s.toCharArray()) { + res[ch - '0']++; + } + return res; + } +} + +``` +*Python3 code* +```python +class Solution: + def findMaxForm(self, strs: List[str], m: int, n: int) -> int: + l = len(strs) + dp = [[0]*(n+1) for _ in range(m+1)] + for i in range(1, l + 1): + count0, count1 = strs[i - 1].count('0'), strs[i - 1].count('1') + for i in reversed(range(count0, m + 1)): + for j in reversed(range(count1, n + 1)): + dp[i][j] = max(dp[i][j], 1 + dp[i - count0][j - count1]) + return dp[m][n] +``` + +## Similar problems + +- [Leetcode 600. Non-negative Integers without Consecutive Ones](https://leetcode.com/problems/non-negative-integers-without-consecutive-ones/) +- [Leetcode 322. Coin Change](https://leetcode.com/problems/coin-change/) + diff --git a/problems/48.rotate-image.md b/problems/48.rotate-image.md new file mode 100644 index 0000000..9cf8f59 --- /dev/null +++ b/problems/48.rotate-image.md @@ -0,0 +1,148 @@ +## 题目地址 + +https://leetcode.com/problems/rotate-image/description/ + +## 题目描述 + +``` +You are given an n x n 2D matrix representing an image. + +Rotate the image by 90 degrees (clockwise). + +Note: + +You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation. + +Example 1: + +Given input matrix = +[ + [1,2,3], + [4,5,6], + [7,8,9] +], + +rotate the input matrix in-place such that it becomes: +[ + [7,4,1], + [8,5,2], + [9,6,3] +] +Example 2: + +Given input matrix = +[ + [ 5, 1, 9,11], + [ 2, 4, 8,10], + [13, 3, 6, 7], + [15,14,12,16] +], + +rotate the input matrix in-place such that it becomes: +[ + [15,13, 2, 5], + [14, 3, 4, 1], + [12, 6, 8, 9], + [16, 7,10,11] +] + +``` + +## 思路 + +这道题目让我们 in-place,也就说空间复杂度要求 O(1),如果没有这个限制的话,很简单。 + +通过观察发现,我们只需要将第 i 行变成第 n - i - 1 列, 因此我们只需要保存一个原有矩阵,然后按照这个规律一个个更新即可。 + +![48.rotate-image-1](../assets/problems/48.rotate-image-1.png) + +代码: + +```js +var rotate = function(matrix) { + // 时间复杂度O(n^2) 空间复杂度O(n) + const oMatrix = JSON.parse(JSON.stringify(matrix)); // clone + const n = oMatrix.length; + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + matrix[j][n - i - 1] = oMatrix[i][j]; + } + } +}; +``` + +如果要求空间复杂度是O(1)的话,我们可以用一个temp记录即可,这个时候就不能逐个遍历了。 +比如遍历到1的时候,我们把1存到temp,然后更新1的值为7。 1被换到了3的位置,我们再将3存到temp,依次类推。 +但是这种解法写起来比较麻烦,这里我就不写了。 + +事实上有一个更加巧妙的做法,我们可以巧妙地利用对称轴旋转达到我们的目的,如图,我们先进行一次以对角线为轴的翻转,然后 +再进行一次以水平轴心线为轴的翻转即可。 + +![48.rotate-image-2](../assets/problems/48.rotate-image-2.png) + +这种做法的时间复杂度是O(n^2) ,空间复杂度是O(1) + +## 关键点解析 + +- 矩阵旋转操作 + +## 代码 + +* 语言支持: Javascript,Python3 + +```js +/* + * @lc app=leetcode id=48 lang=javascript + * + * [48] Rotate Image + */ +/** + * @param {number[][]} matrix + * @return {void} Do not return anything, modify matrix in-place instead. + */ +var rotate = function(matrix) { + // 时间复杂度O(n^2) 空间复杂度O(1) + + // 做法: 先沿着对角线翻转,然后沿着水平线翻转 + const n = matrix.length; + function swap(arr, [i, j], [m, n]) { + const temp = arr[i][j]; + arr[i][j] = arr[m][n]; + arr[m][n] = temp; + } + for (let i = 0; i < n - 1; i++) { + for (let j = 0; j < n - i; j++) { + swap(matrix, [i, j], [n - j - 1, n - i - 1]); + } + } + + for (let i = 0; i < n / 2; i++) { + for (let j = 0; j < n; j++) { + swap(matrix, [i, j], [n - i - 1, j]); + } + } +}; +``` +Python3 Code: +```Python +class Solution: + def rotate(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + 先做矩阵转置(即沿着对角线翻转),然后每个列表翻转; + """ + n = len(matrix) + for i in range(n): + for j in range(i, n): + matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] + for m in matrix: + m.reverse() + + def rotate2(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + 通过内置函数zip,可以简单实现矩阵转置,下面的代码等于先整体翻转,后转置; + 不过这种写法的空间复杂度其实是O(n); + """ + matrix[:] = map(list, zip(*matrix[::-1])) +``` diff --git a/problems/49.group-anagrams.md b/problems/49.group-anagrams.md new file mode 100644 index 0000000..e8c7c69 --- /dev/null +++ b/problems/49.group-anagrams.md @@ -0,0 +1,122 @@ +## 题目地址 + +https://leetcode.com/problems/group-anagrams/description/ + +## 题目描述 + +``` +Given an array of strings, group anagrams together. + +Example: + +Input: ["eat", "tea", "tan", "ate", "nat", "bat"], +Output: +[ + ["ate","eat","tea"], + ["nat","tan"], + ["bat"] +] +Note: + +All inputs will be in lowercase. +The order of your output does not matter. +``` + +## 思路 + +一个简单的解法就是遍历数组,然后对每一项都进行排序,然后将其添加到 hashTable 中,最后输出 hashTable 中保存的值即可。 + +这种做法空间复杂度 O(n), 假设排序算法用的快排,那么时间复杂度为 O(n \* klogk), n 为数组长度,k 为字符串的平均长度 + +代码: + +```js +var groupAnagrams = function(strs) { + const hashTable = {}; + + function sort(str) { + return str + .split("") + .sort() + .join(""); + } + + // 这个方法需要排序,因此不是很优,但是很直观,容易想到 + for (let i = 0; i < strs.length; i++) { + const str = strs[i]; + const key = sort(str); + if (!hashTable[key]) { + hashTable[key] = [str]; + } else { + hashTable[key].push(str); + } + } + + return Object.values(hashTable); +}; +``` + +下面我们介绍另外一种方法,我们建立一个 26 长度的 counts 数组(如果区分大小写,我们可以建立 52 个,如果支持其他字符依次类推)。 +然后我们给每一个字符一个固定的数组下标,然后我们只需要更新每个字符出现的次数。 最后形成的 counts 数组如果一致,则说明他们可以通过 +交换顺序得到。这种算法空间复杂度 O(n), 时间复杂度 O(n \* k), n 为数组长度,k 为字符串的平均长度. + +![49.group-anagrams](../assets/problems/49.group-anagrams.png) + +## 关键点解析 + +- 桶排序 + +## 代码 + +* 语言支持: Javascript,Python3 + +```js +/* + * @lc app=leetcode id=49 lang=javascript + * + * [49] Group Anagrams + */ +/** + * @param {string[]} strs + * @return {string[][]} + */ +var groupAnagrams = function(strs) { + // 类似桶排序 + + let counts = []; + const hashTable = {}; + for (let i = 0; i < strs.length; i++) { + const str = strs[i]; + counts = Array(26).fill(0); + for (let j = 0; j < str.length; j++) { + counts[str[j].charCodeAt(0) - "a".charCodeAt(0)]++; + } + const key = counts.join(""); + if (!hashTable[key]) { + hashTable[key] = [str]; + } else { + hashTable[key].push(str); + } + } + + return Object.values(hashTable); +}; +``` +Python3 Code: +```Python +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + """ + 思路同上,在Python中,这里涉及到3个知识点: + 1. 使用内置的 defaultdict 字典设置默认值; + 2. 内置的 ord 函数,计算ASCII值(等于chr)或Unicode值(等于unichr); + 3. 列表不可哈希,不能作为字典的键,因此这里转为元组; + """ + str_dict = collections.defaultdict(list) + for s in strs: + s_key = [0] * 26 + for c in s: + s_key[ord(c)-ord('a')] += 1 + str_dict[tuple(s_key)].append(s) + return str_dict.values() +``` diff --git a/problems/493.reverse-pairs.md b/problems/493.reverse-pairs.md new file mode 100644 index 0000000..0470c97 --- /dev/null +++ b/problems/493.reverse-pairs.md @@ -0,0 +1,120 @@ +## 题目地址(493. 翻转对) + +https://leetcode-cn.com/problems/reverse-pairs/description/ + +## 题目描述 + +``` +给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。 + +你需要返回给定数组中的重要翻转对的数量。 + +示例 1: + +输入: [1,3,2,3,1] +输出: 2 +示例 2: + +输入: [2,4,3,5,1] +输出: 3 +注意: + +给定数组的长度不会超过50000。 +输入数组中的所有数字都在32位整数的表示范围内。 + +``` + +## 暴力法 + +### 思路 + +读完这道题你应该就能联想到逆序数才行。求解逆序数最简单的做法是使用双层循环暴力求解。我们仿照求解决逆序数的解法来解这道题(其实唯一的区别就是系数从 1 变成了 2)。 + +### 代码 + +代码支持:Python3 + +Python3 Code: + +```python +class Solution(object): + def reversePairs(self, nums): + n = len(nums) + cnt = 0 + for i in range(n): + for j in range(i + 1, n): + if nums[i] > 2 * nums[j]: + cnt += 1 + return cnt +``` + +## 分治法 + +### 思路 + +如果你能够想到逆序数,那么你很可能直到使用类似归并排序的方法可以求解逆序数。实际上逆序数只是归并排序的副产物而已。 + +我们在正常的归并排序的代码中去计算逆序数即可。由于每次分治的过程,左右两段数组分别是有序的,因此我们可以减少一些运算。 从时间复杂度的角度上讲,我们从$O(N^2)$优化到了 $O(NlogN)$。 + +具体来说,对两段有序的数组,有序数组内部是不需要计算逆序数的。 我们计算逆序数的逻辑只是计算两个数组之间的逆序数,我们假设两个数组是 A 和 B,并且 A 数组最大的元素不大于 B 数组最小的元素。而要做到这样,我们只需要常规的归并排序即可。 + +接下来问题转化为求解两个有序数组之间的逆序数,并且两个有序数组之间满足关系`A数组最大的元素不大于B数组最小的元素`。 + +关于计算逆序数的核心代码(Python3): + +```python +l = r = 0 +while l < len(left) and r < len(right): + if left[l] <= 2 * right[r]: + l += 1 + else: + self.cnt += len(left) - l + r += 1 +``` + +### 代码 + +代码支持:Python3 + +Python3 Code: + +```python +class Solution(object): + def reversePairs(self, nums): + self.cnt = 0 + + def mergeSort(lst): + L = len(lst) + if L <= 1: + return lst + return mergeTwo(mergeSort(lst[:L//2]), mergeSort(lst[L//2:])) + + def mergeTwo(left, right): + l = r = 0 + while l < len(left) and r < len(right): + if left[l] <= 2 * right[r]: + l += 1 + else: + self.cnt += len(left) - l + r += 1 + return sorted(left+right) + + mergeSort(nums) + return self.cnt + +``` + +对于具体的排序过程我们偷懒直接使用了语言内置的方法 sorted,这在很多时候是有用的,即使你是参加面试,这种方式通常也是允许的。省略非核心的考点,可以使得问题更加聚焦,这是一种解决问题的思路,在工作中也很有用。 + +## 关键点解析 + +- 归并排序 +- 逆序数 +- 分治 +- 识别考点,其他非重点可以使用语言内置方法 + +## 代码 + +## 扩展 + +这道题还有很多别的解法,感性的可以参考下这个题解 [General principles behind problems similar to "Reverse Pairs"](https://leetcode.com/problems/reverse-pairs/discuss/97268/General-principles-behind-problems-similar-to-%22Reverse-Pairs%22) diff --git a/problems/494.target-sum.md b/problems/494.target-sum.md new file mode 100644 index 0000000..6611e25 --- /dev/null +++ b/problems/494.target-sum.md @@ -0,0 +1,98 @@ +## 题目地址 + +https://leetcode.com/problems/target-sum/description/ + +## 题目描述 + +``` +You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol. + +Find out how many ways to assign symbols to make sum of integers equal to target S. + +Example 1: +Input: nums is [1, 1, 1, 1, 1], S is 3. +Output: 5 +Explanation: + +-1+1+1+1+1 = 3 ++1-1+1+1+1 = 3 ++1+1-1+1+1 = 3 ++1+1+1-1+1 = 3 ++1+1+1+1-1 = 3 + +There are 5 ways to assign symbols to make the sum of nums be target 3. +Note: +The length of the given array is positive and will not exceed 20. +The sum of elements in the given array will not exceed 1000. +Your output answer is guaranteed to be fitted in a 32-bit integer. + +``` + +## 思路 + +题目是给定一个数组,让你在数字前面添加 `+`或者`-`,使其和等于 target. + +![494.target-sum](../assets/problems/494.target-sum.png) + +暴力法的时间复杂度是指数级别的,因此我们不予考虑。我们需要换种思路. + +我们将最终的结果分成两组,一组是我们添加了`+`的,一组是我们添加了`-`的。 + +![494.target-sum-2](../assets/problems/494.target-sum-2.png) + +如上图,问题转化为如何求其中一组,我们不妨求前面标`+`的一组 + +> 如果求出一组,另一组实际就已知了,即总集和这一组的差集。 + +通过进一步分析,我们得到了这样的关系: + +![494.target-sum-3](../assets/problems/494.target-sum-3.png) + +因此问题转化为,求解`sumCount(nums, target)`,即 nums 数组中能够组成 +target 的总数一共有多少种,这是一道我们之前做过的题目,使用动态规划可以解决。 + +## 关键点解析 + +- 对元素进行分组,分组的依据是符号, 是`+` 或者 `-` +- 通过数学公式推导可以简化我们的求解过程,这需要一点`数学知识和数学意识` + +## 代码 + +```js +/* + * @lc app=leetcode id=494 lang=javascript + * + * [494] Target Sum + * + */ +// 这个是我们熟悉的问题了 +// 我们这里需要求解的是nums里面有多少种可以组成target的方式 +var sumCount = function(nums, target) { + // 这里通过观察,我们没必要使用二维数组去存储这些计算结果 + // 使用一维数组可以有效节省空间 + const dp = Array(target + 1).fill(0); + dp[0] = 1; + for (let i = 0; i < nums.length; i++) { + for (let j = target; j >= nums[i]; j--) { + dp[j] += dp[j - nums[i]]; + } + } + return dp[target]; +}; +const add = nums => nums.reduce((a, b) => (a += b), 0); +/** + * @param {number[]} nums + * @param {number} S + * @return {number} + */ +var findTargetSumWays = function(nums, S) { + const sum = add(nums); + if (sum < S) return 0; + if ((S + sum) % 2 === 1) return 0; + return sumCount(nums, (S + sum) >> 1); +}; +``` + +## 扩展 + +如果这道题目并没有限定所有的元素以及 target 都是正数怎么办? diff --git a/problems/5.longest-palindromic-substring.md b/problems/5.longest-palindromic-substring.md new file mode 100644 index 0000000..0edaffc --- /dev/null +++ b/problems/5.longest-palindromic-substring.md @@ -0,0 +1,134 @@ +## 题目地址(5. 最长回文子串) + +https://leetcode-cn.com/problems/longest-palindromic-substring/ + +## 题目描述 + +给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 + +示例 1: + +输入: "babad" +输出: "bab" +注意: "aba" 也是一个有效答案。 +示例 2: + +输入: "cbbd" +输出: "bb" + +## 思路 + +这是一道最长回文的题目,要我们求出给定字符串的最大回文子串。 + +![5.longest-palindromic-substring](../assets/problems/5.longest-palindromic-substring-1.png) + +解决这类问题的核心思想就是两个字“延伸”,具体来说 + +- 如果在一个不是回文字符串的字符串两端添加任何字符,或者在回文串左右分别加不同的字符,得到的一定不是回文串 +- 如果一个字符串不是回文串,或者在回文串左右分别加不同的字符,得到的一定不是回文串 + +事实上,上面的分析已经建立了大问题和小问题之间的关联, +基于此,我们可以建立动态规划模型。 + +我们可以用 dp[i][j] 表示 s 中从 i 到 j(包括 i 和 j)是否可以形成回文, +状态转移方程只是将上面的描述转化为代码即可: + +```js +if (s[i] === s[j] && dp[i + 1][j - 1]) { + dp[i][j] = true; +} +``` +![5.longest-palindromic-substring-2](../assets/problems/5.longest-palindromic-substring-2.png) + +base case就是一个字符(轴对称点是本身),或者两个字符(轴对称点是介于两者之间的虚拟点)。 + +![5.longest-palindromic-substring-3](../assets/problems/5.longest-palindromic-substring-3.png) +## 关键点 + +- ”延伸“(extend) + +## 代码 + +代码支持:Python,JavaScript: + +Python Code: + +```python +class Solution: + def longestPalindrome(self, s: str) -> str: + n = len(s) + if n == 0: + return "" + res = s[0] + def extend(i, j, s): + while(i >= 0 and j < len(s) and s[i] == s[j]): + i -= 1 + j += 1 + return s[i + 1:j] + + for i in range(n - 1): + e1 = extend(i, i, s) + e2 = extend(i, i + 1, s) + if max(len(e1), len(e2)) > len(res): + res = e1 if len(e1) > len(e2) else e2 + return res +``` + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=5 lang=javascript + * + * [5] Longest Palindromic Substring + */ +/** + * @param {string} s + * @return {string} + */ +var longestPalindrome = function(s) { + // babad + // tag : dp + if (!s || s.length === 0) return ""; + let res = s[0]; + + const dp = []; + + // 倒着遍历简化操作, 这么做的原因是dp[i][..]依赖于dp[i + 1][..] + for (let i = s.length - 1; i >= 0; i--) { + dp[i] = []; + for (let j = i; j < s.length; j++) { + if (j - i === 0) dp[i][j] = true; + // specail case 1 + else if (j - i === 1 && s[i] === s[j]) dp[i][j] = true; + // specail case 2 + else if (s[i] === s[j] && dp[i + 1][j - 1]) { + // state transition + dp[i][j] = true; + } + + if (dp[i][j] && j - i + 1 > res.length) { + // update res + res = s.slice(i, j + 1); + } + } + } + + return res; +}; +``` + + +***复杂度分析*** +- 时间复杂度:$O(N^2)$ +- 空间复杂度:$O(N^2)$ + +更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经30K star啦。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) + +## 相关题目 + +- [516.longest-palindromic-subsequence](./516.longest-palindromic-subsequence.md) diff --git a/problems/50.pow-x-n.md b/problems/50.pow-x-n.md new file mode 100644 index 0000000..42291b1 --- /dev/null +++ b/problems/50.pow-x-n.md @@ -0,0 +1,189 @@ +## 题目地址(50. Pow(x, n)) + +https://leetcode-cn.com/problems/powx-n/description/ + +## 题目描述 + +``` +实现 pow(x, n) ,即计算 x 的 n 次幂函数。 + +示例 1: + +输入: 2.00000, 10 +输出: 1024.00000 +示例 2: + +输入: 2.10000, 3 +输出: 9.26100 +示例 3: + +输入: 2.00000, -2 +输出: 0.25000 +解释: 2-2 = 1/22 = 1/4 = 0.25 +说明: + +-100.0 < x < 100.0 +n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。 + +``` + +## 解法零 - 遍历法 + +### 思路 + +这道题是让我们实现数学函数`幂`,因此直接调用系统内置函数是不被允许的。 + +符合直觉的做法是`将x乘以n次`,这种做法的时间复杂度是$O(N)$。 + +经实际测试,这种做法果然超时了。测试用例通过 291/304,在 `0.00001\n2147483647`这个测试用例挂掉了。如果是面试,这个解法可以作为一种兜底解法。 + +### 代码 + +语言支持: Python3 + +Python3 Code: + +```python +class Solution: + def myPow(self, x: float, n: int) -> float: + if n == 0: + return 1 + if n < 0: + return 1 / self.myPow(x, -n) + res = 1 + for _ in range(n): + res *= x + return res +``` + +## 解法一 - 普通递归(超时法) + +### 思路 + +首先我们要知道: + +- 如果想要求 x ^ 4,那么我们可以求 (x^2)^2 +- 如果是奇数,会有一点不同。 比如 x ^ 5 就等价于 x \* (x^2)^2。 + +> 当然 x ^ 5 可以等价于 (x ^ 2) ^ 2.5, 但是这不相当于直接调用了幂函数了么。对于整数,我们可以很方便的模拟,但是小数就不方便了。 + +我们的思路就是: + +- 将 n 地板除 2,我们不妨设结果为 a +- 那么 myPow(x, n) 就等价于 `myPow(x, a) * myPow(x, n - a)` + +很可惜这种算法也会超时,原因在于重复计算会比较多,你可以试一下缓存一下计算看能不能通过。 + +> 如果你搞不清楚有哪些重复计算,建议画图理解一下。 + +### 代码 + +语言支持: Python3 + +Python3 Code: + +```python +class Solution: + def myPow(self, x: float, n: int) -> float: + if n == 0: + return 1 + if n == 1: + return x + if n < 0: + return 1 / self.myPow(x, -n) + return self.myPow(x, n // 2) * self.myPow(x, n - n // 2) +``` + +## 解法二 - 优化递归 + +### 思路 + +上面的解法每次直接 myPow 都会调用两次自己。我们不从缓存计算角度,而是从减少这种调用的角度来优化。 + +我们考虑 myPow 只调用一次自身可以么? 没错,是可以的。 + +我们的思路就是: + +- 如果 n 是偶数,我们将 n 折半,底数变为 x^2 +- 如果 n 是奇数, 我们将 n 减去 1 ,底数不变,得到的结果再乘上底数 x + +这样终于可以 AC。 + +### 代码 + +语言支持: Python3 + +Python3 Code: + +```python +class Solution: + def myPow(self, x: float, n: int) -> float: + if n == 0: + return 1 + if n == 1: + return x + if n < 0: + return 1 / self.myPow(x, -n) + return self.myPow(x _ x, n // 2) if n % 2 == 0 else x _ self.myPow(x, n - 1) +``` + +## 解法三 - 位运算 + +### 思路 + +我们来从位(bit)的角度来看一下这道题。如果你经常看我的题解和文章的话,可能知道我之前写过几次相关的“从位的角度思考分治法”,比如 LeetCode [458.可怜的小猪](https://leetcode-cn.com/problems/poor-pigs/description/)。 + +以 x 的 10 次方举例。10 的 2 进制是 1010,然后用 2 进制转 10 进制的方法把它展成 2 的幂次的和。 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gbdseolzbmj30t802mjrk.jpg) + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gbdssrrsh6j30xp0u040e.jpg) + +因此我们的算法就是: + +- 不断的求解 x 的 2^0 次方,x 的 2^1 次方,x 的 2^2 次方等等。 +- 将 n 转化为二进制表示 +- 将 n 的二进制表示中`1的位置`pick 出来。比如 n 的第 i 位为 1,那么就将 x^i pick 出来。 +- 将 pick 出来的结果相乘 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gbdtiky90rj30vq0hcab4.jpg) + +这里有两个问题: + +第一个问题是`似乎我们需要存储 x^i 以便后续相乘的时候用到`。实际上,我们并不需要这么做。我们可以采取一次遍历的方式来完成,具体看代码。 + +第二个问题是,如果我们从低位到高位计算的时候,我们如何判断最高位置是否为 1?我们需要一个 bitmask 来完成,这种算法我们甚至需要借助一个额外的变量。 然而我们可以 hack 一下,直接从高位到低位进行计算,这个时候我们只需要判断最后一位是否为 1 就可以了,这个就简单了,我们直接和 1 进行一次`与运算`即可。 + +### 代码 + +语言支持: Python3 + +Python3 Code: + +```python +class Solution: + def myPow(self, x: float, n: int) -> float: + if n < 0: + return 1 / self.myPow(x, -n) + res = 1 + while n: + if n & 1 == 1: + res *= x + x *= x + n >>= 1 + return res +``` + +## 关键点解析 + +- 超时分析 +- hashtable +- 数学分析 +- 位运算 +- 二进制转十进制 + +## 相关题目 + +- [458.可怜的小猪](https://leetcode-cn.com/problems/poor-pigs/description/) + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbdrfn9n2wj30wn0u0dp2.jpg) diff --git a/problems/501.Find-Mode-in-Binary-Search-Tree.md b/problems/501.Find-Mode-in-Binary-Search-Tree.md new file mode 100644 index 0000000..ab93db0 --- /dev/null +++ b/problems/501.Find-Mode-in-Binary-Search-Tree.md @@ -0,0 +1,89 @@ +## Problem on Leetcode + +https://leetcode.com/problems/find-mode-in-binary-search-tree/ + +## Description + +Given a binary search tree (BST) with duplicates, find all the mode(s) (the most frequently occurred element) in the given BST. + +Assume a BST is defined as follows: + +- The left subtree of a node contains only nodes with keys less than or equal to the node's key. +- The right subtree of a node contains only nodes with keys greater than or equal to the node's key. +- Both the left and right subtrees must also be binary search trees. + +For example: +Given BST `[1,null,2,2]`, + +```bash + 1 + \ + 2 + / + 2 +``` + +return `[2]`. + +Note: If a tree has more than one mode, you can return them in any order. + +Follow up: Could you do that without using any extra space? (Assume that the implicit stack space incurred due to recursion does not count). + +## Ideas + +Basically, it needs traversing, counting and recording. `map` can be used to help us to do record and count. +For doing that without using any extra space, the property of BST will be used. For each node, the value of the left child is no greater than its value, while the value of right child is no less than the its value. So, when traversing each node, only the value of previous node is required to be compared with the value of current node for counting. +As the problem shown, an array of intergers will be returned. While the number of modes is unknown, using `ArrayList` to store all outputs is a good choice because `ArrayList` can be converted into array conveniently. + +## Codes + +Supported Language: `Java` + +- Java Code: + +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +class Solution { + List list = new ArrayList<> (); + TreeNode preNode = null; + int max = 0, count = 0; + + public int[] findMode(TreeNode root) { + helper(root); + int[] res = new int[list.size()]; + for (int i=0; i max) { + list.clear(); + list.add(root.val); + max = count; + } else if (max == count) { + list.add(root.val); + } + preNode = root; + helper(root.right); + } +} +``` \ No newline at end of file diff --git a/problems/516.longest-palindromic-subsequence.md b/problems/516.longest-palindromic-subsequence.md new file mode 100644 index 0000000..17a0b20 --- /dev/null +++ b/problems/516.longest-palindromic-subsequence.md @@ -0,0 +1,96 @@ +## 题目地址 + +https://leetcode.com/problems/longest-palindromic-subsequence/description/ + +## 题目描述 + +``` +Given a string s, find the longest palindromic subsequence's length in s. You may assume that the maximum length of s is 1000. + +Example 1: +Input: + +"bbbab" +Output: +4 +One possible longest palindromic subsequence is "bbbb". +Example 2: +Input: + +"cbbd" +Output: +2 +One possible longest palindromic subsequence is "bb". +``` + +## 思路 + +这是一道最长回文的题目,要我们求出给定字符串的最大回文子序列。 + +![516.longest-palindromic-subsequence-1](../assets/problems/516.longest-palindromic-subsequence-1.png) + +解决这类问题的核心思想就是两个字“延伸”,具体来说 + +- 如果一个字符串是回文串,那么在它左右分别加上一个相同的字符,那么它一定还是一个回文串,因此`回文长度增加2` +- 如果一个字符串不是回文串,或者在回文串左右分别加不同的字符,得到的一定不是回文串,因此`回文长度不变,我们取[i][j-1]和[i+1][j]的较大值` + +![516.longest-palindromic-subsequence-2](../assets/problems/516.longest-palindromic-subsequence-2.png) + +事实上,上面的分析已经建立了大问题和小问题之间的关联, +基于此,我们可以建立动态规划模型。 + +我们可以用 dp[i][j] 表示 s 中从 i 到 j(包括 i 和 j)的回文序列长度, +状态转移方程只是将上面的描述转化为代码即可: + +```js +if (s[i] === s[j]) { + dp[i][j] = dp[i + 1][j - 1] + 2; +} else { + dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]); +} +``` + +base case 就是一个字符(轴对称点是本身) + +![516.longest-palindromic-subsequence-3](../assets/problems/516.longest-palindromic-subsequence-3.png) + +## 关键点 + +- ”延伸“(extend) + +## 代码 + +```js +/* + * @lc app=leetcode id=516 lang=javascript + * + * [516] Longest Palindromic Subsequence + */ +/** + * @param {string} s + * @return {number} + */ +var longestPalindromeSubseq = function(s) { + // bbbab 返回4 + // tag : dp + const dp = []; + + for (let i = s.length - 1; i >= 0; i--) { + dp[i] = Array(s.length).fill(0); + for (let j = i; j < s.length; j++) { + if (i - j === 0) dp[i][j] = 1; + else if (s[i] === s[j]) { + dp[i][j] = dp[i + 1][j - 1] + 2; + } else { + dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]); + } + } + } + + return dp[0][s.length - 1]; +}; +``` + +## 相关题目 + +- [5.longest-palindromic-substring](./5.longest-palindromic-substring.md) diff --git a/problems/518.coin-change-2.md b/problems/518.coin-change-2.md new file mode 100644 index 0000000..2eee7bb --- /dev/null +++ b/problems/518.coin-change-2.md @@ -0,0 +1,197 @@ +## 题目地址(518. 零钱兑换 II) + +https://leetcode-cn.com/problems/coin-change-2/description/ + +## 题目描述 + +给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 + +示例 1: + +输入: amount = 5, coins = [1, 2, 5] +输出: 4 +解释: 有四种方式可以凑成总金额: +5=5 +5=2+2+1 +5=2+1+1+1 +5=1+1+1+1+1 +示例 2: + +输入: amount = 3, coins = [2] +输出: 0 +解释: 只用面额 2 的硬币不能凑成总金额 3。 +示例 3: + +输入: amount = 10, coins = [10] +输出: 1 + +注意: + +你可以假设: + +0 <= amount (总金额) <= 5000 +1 <= coin (硬币面额) <= 5000 +硬币种类不超过 500 种 +结果符合 32 位符号整数 + +## 思路 + +这个题目和 coin-change 的思路比较类似。 + +我们还是按照 coin-change 的思路来, 如果将问题画出来大概是这样: + +![](https://tva1.sinaimg.cn/large/0082zybply1gcb7ezp3juj30fi0c1ta6.jpg) + +进一步我们可以对问题进行空间复杂度上的优化(这种写法比较难以理解,但是相对更省空间) + +![](https://tva1.sinaimg.cn/large/0082zybply1gcb7fc1qtvj30ix0ay0u8.jpg) + +> 这里用动图会更好理解, 有时间的话我会做一个动图出来, 现在大家脑补一下吧 + +## 关键点解析 + +- 动态规划 + +- 子问题 + +用 dp[i] 来表示组成 i 块钱,需要最少的硬币数,那么 + +1. 第 j 个硬币我可以选择不拿 这个时候, 组成数 = dp[i] + +2. 第 j 个硬币我可以选择拿 这个时候, 组成数 = dp[i - coins[j]] + dp[i] + +- 和背包问题不同, 硬币是可以拿任意个 + +- 对于每一个 dp[i] 我们都选择遍历一遍 coin, 不断更新 dp[i] + +eg: + +```js +if (amount === 0) return 1; + +const dp = [Array(amount + 1).fill(1)]; + +for (let i = 1; i < amount + 1; i++) { + dp[i] = Array(coins.length + 1).fill(0); + for (let j = 1; j < coins.length + 1; j++) { + // 从1开始可以简化运算 + if (i - coins[j - 1] >= 0) { + // 注意这里是coins[j -1]而不是coins[j] + dp[i][j] = dp[i][j - 1] + dp[i - coins[j - 1]][j]; // 由于可以重复使用硬币所以这里是j不是j-1 + } else { + dp[i][j] = dp[i][j - 1]; + } + } +} + +return dp[dp.length - 1][coins.length]; +``` + +- 当我们选择一维数组去解的时候,内外循环将会对结果造成影响 + +![](https://tva1.sinaimg.cn/large/0082zybply1gcb7fjfon6j30j00bddh2.jpg) + +eg: + +```js +// 这种答案是不对的。 +// 原因在于比如amount = 5, coins = [1,2,5] +// 这种算法会将[1,2,2] [2,1,2] [2, 2, 1] 算成不同的 + +if (amount === 0) return 1; + +const dp = [1].concat(Array(amount).fill(0)); + +for (let i = 1; i < amount + 1; i++) { + for (let j = 0; j < coins.length; j++) { + if (i - coins[j] >= 0) { + dp[i] = dp[i] + dp[i - coins[j]]; + } + } +} + +return dp[dp.length - 1]; + +// 正确的写法应该是内外循环调换一下, 具体可以看下方代码区 +``` + +## 代码 + + +代码支持:Python3,JavaScript: + + +JavaSCript Code: + +```js +/* + * @lc app=leetcode id=518 lang=javascript + * + * [518] Coin Change 2 + * + */ +/** + * @param {number} amount + * @param {number[]} coins + * @return {number} + */ +var change = function(amount, coins) { + if (amount === 0) return 1; + + const dp = [1].concat(Array(amount).fill(0)); + + for (let j = 0; j < coins.length; j++) { + for (let i = 1; i < amount + 1; i++) { + if (i - coins[j] >= 0) { + dp[i] = dp[i] + dp[i - coins[j]]; + } + } + } + + return dp[dp.length - 1]; +}; +``` + +Python Code: + +```python +class Solution: + def change(self, amount: int, coins: List[int]) -> int: + dp = [0] * (amount + 1) + dp[0] = 1 + + for j in range(len(coins)): + for i in range(1, amount + 1): + if i >= coins[j]: + dp[i] += dp[i - coins[j]] + + return dp[-1] +``` + +## 扩展 + +这是一道很简单描述的题目, 因此很多时候会被用到大公司的电面中。 + +相似问题: + +[322.coin-change](./322.coin-change.md) + +## 附录 + +Python 二维解法(不推荐,但是可以帮助理解): + +```python +class Solution: + def change(self, amount: int, coins: List[int]) -> int: + dp = [[0 for _ in range(len(coins) + 1)] for _ in range(amount + 1)] + for j in range(len(coins) + 1): + dp[0][j] = 1 + + for i in range(amount + 1): + for j in range(1, len(coins) + 1): + if i >= coins[j - 1]: + dp[i][j] = dp[i - coins[j - 1]][j] + dp[i][j - 1] + else: + dp[i][j] = dp[i][j - 1] + return dp[-1][-1] +``` diff --git a/problems/52.N-Queens-II.md b/problems/52.N-Queens-II.md new file mode 100644 index 0000000..e3175ea --- /dev/null +++ b/problems/52.N-Queens-II.md @@ -0,0 +1,83 @@ +## 题目地址 +https://leetcode-cn.com/problems/n-queens-ii + +## 题目描述 +``` +n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + +给定一个整数 n,返回 n 皇后不同的解决方案的数量。 + +示例: + +输入: 4 +输出: 2 +解释: 4 皇后问题存在如下两个不同的解法。 + +[ + [".Q..",  // 解法 1 +  "...Q", +  "Q...", +  "..Q."], + + ["..Q.",  // 解法 2 +  "Q...", +  "...Q", +  ".Q.."] +] +``` + +## 思路 +使用深度优先搜索配合位运算,二进制为 1 代表不可放置,0 相反 + +利用如下位运算公式: + +- x & -x :得到最低位的 1 代表除最后一位 1 保留,其他位全部为 0 +- x & (x-1):清零最低位的 1 代表将最后一位 1 变成 0 +- x & ((1 << n) - 1):将 x 最高位至第 n 位(含)清零 + +## 关键点 + +- 位运算 +- DFS(深度优先搜索) + +## 代码 +* 语言支持:JS + +```js +/** + * @param {number} n + * @return {number} + * @param row 当前层 + * @param cols 列 + * @param pie 左斜线 + * @param na 右斜线 + */ +const totalNQueens = function (n) { + let res = 0; + const dfs = (n, row, cols, pie, na) => { + if (row >= n) { + res++; + return; + } + // 将所有能放置 Q 的位置由 0 变成 1,以便进行后续的位遍历 + // 也就是得到当前所有的空位 + let bits = (~(cols | pie | na)) & ((1 << n) - 1); + while (bits) { + // 取最低位的1 + let p = bits & -bits; + // 把P位置上放入皇后 + bits = bits & (bits - 1); + // row + 1 搜索下一行可能的位置 + // cols | p 目前所有放置皇后的列 + // (pie | p) << 1 和 (na | p) >> 1) 与已放置过皇后的位置 位于一条斜线上的位置 + dfs(n, row + 1, cols | p, (pie | p) << 1, (na | p) >> 1); + } + } + dfs(n, 0, 0, 0, 0); + return res; +}; +``` +***复杂度分析*** + +- 时间复杂度:O(N!) +- 空间复杂度:O(N) diff --git a/problems/53.maximum-sum-subarray-cn.md b/problems/53.maximum-sum-subarray-cn.md new file mode 100644 index 0000000..7f09c6c --- /dev/null +++ b/problems/53.maximum-sum-subarray-cn.md @@ -0,0 +1,369 @@ +## 题目地址 +https://leetcode.com/problems/maximum-subarray/ + +## 题目描述 +``` +Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum. + +Example: + +Input: [-2,1,-3,4,-1,2,1,-5,4], +Output: 6 +Explanation: [4,-1,2,1] has the largest sum = 6. +Follow up: + +If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle. +``` + +## 思路 + +这道题求解连续最大子序列和,以下从时间复杂度角度分析不同的解题思路。 + +#### 解法一 - 暴力解 (暴力出奇迹, 噢耶!) +一般情况下,先从暴力解分析,然后再进行一步步的优化。 + +**原始暴力解:**(超时) + +求子序列和,那么我们要知道子序列的首尾位置,然后计算首尾之间的序列和。用2个for循环可以枚举所有子序列的首尾位置。 +然后用一个for循环求解序列和。这里时间复杂度太高,`O(n^3)`. + +#### 复杂度分析 +- *时间复杂度:* `O(n^3) - n 是数组长度` +- *空间复杂度:* `O(1)` + +#### 解法二 - 前缀和 + 暴力解 +**优化暴力解:** (震惊,居然AC了) + +在暴力解的基础上,用前缀和我们可以优化到暴力解`O(n^2)`, 这里以空间换时间。 +这里可以使用原数组表示`prefixSum`, 省空间。 + +求序列和可以用前缀和(`prefixSum`) 来优化,给定子序列的首尾位置`(l, r),` +那么序列和 `subarraySum=prefixSum[r] - prefixSum[l - 1];` +用一个全局变量`maxSum`, 比较每次求解的子序列和,`maxSum = max(maxSum, subarraySum)`. + +#### 复杂度分析 +- *时间复杂度:* `O(n^2) - n 是数组长度` +- *空间复杂度:* `O(n) - prefixSum 数组空间为n` + +>如果用更改原数组表示前缀和数组,空间复杂度降为`O(1)` + +但是时间复杂度还是太高,还能不能更优化。答案是可以,前缀和还可以优化到`O(n)`. + +#### 解法三 - 优化前缀和 - from [**@lucifer**](https://github.com/azl397985856) + +我们定义函数` S(i)` ,它的功能是计算以 `0(包括 0)`开始加到 `i(包括 i)`的值。 + +那么 `S(j) - S(i - 1)` 就等于 从 `i` 开始(包括 i)加到 `j`(包括 j)的值。 + +我们进一步分析,实际上我们只需要遍历一次计算出所有的 `S(i)`, 其中 `i = 0,1,2....,n-1。` +然后我们再减去之前的` S(k)`,其中 `k = 0,1,i - 1`,中的最小值即可。 因此我们需要 +用一个变量来维护这个最小值,还需要一个变量维护最大值。 + +#### 复杂度分析 +- *时间复杂度:* `O(n) - n 是数组长度` +- *空间复杂度:* `O(1)` + +#### 解法四 - [分治法](https://www.wikiwand.com/zh-hans/%E5%88%86%E6%B2%BB%E6%B3%95) + +我们把数组`nums`以中间位置(`m`)分为左(`left`)右(`right`)两部分. 那么有, +`left = nums[0]...nums[m - 1]` 和 `right = nums[m + 1]...nums[n-1]` + +最大子序列和的位置有以下三种情况: +1. 考虑中间元素`nums[m]`, 跨越左右两部分,这里从中间元素开始,往左求出后缀最大,往右求出前缀最大, 保持连续性。 +2. 不考虑中间元素,最大子序列和出现在左半部分,递归求解左边部分最大子序列和 +3. 不考虑中间元素,最大子序列和出现在右半部分,递归求解右边部分最大子序列和 + +分别求出三种情况下最大子序列和,三者中最大值即为最大子序列和。 + +举例说明,如下图: +![](https://tva1.sinaimg.cn/large/0082zybply1gbv3hguiadj31400u044t.jpg) + +#### 复杂度分析 +- *时间复杂度:* `O(nlogn) - n 是数组长度` +- *空间复杂度:* `O(logn)` - 因为调用栈的深度最多是logn。 + +#### 解法五 - [动态规划](https://www.wikiwand.com/zh-hans/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92) +动态规划的难点在于找到状态转移方程, + +`dp[i] - 表示到当前位置 i 的最大子序列和` + +状态转移方程为: +`dp[i] = max(dp[i - 1] + nums[i], nums[i])` + +初始化:`dp[0] = nums[0]` + +从状态转移方程中,我们只关注前一个状态的值,所以不需要开一个数组记录位置所有子序列和,只需要两个变量, + +`currMaxSum - 累计最大和到当前位置i` + +`maxSum - 全局最大子序列和`: + +- `currMaxSum = max(currMaxSum + nums[i], nums[i])` +- `maxSum = max(currMaxSum, maxSum)` + +如图: +![](https://tva1.sinaimg.cn/large/0082zybply1gbv3hpz9tvj30pj0h2dh1.jpg) + +#### 复杂度分析 +- *时间复杂度:* `O(n) - n 是数组长度` +- *空间复杂度:* `O(1)` + +## 关键点分析 +1. 暴力解,列举所有组合子序列首尾位置的组合,求解最大的子序列和, 优化可以预先处理,得到前缀和 +2. 分治法,每次从中间位置把数组分为左右中三部分, 分别求出左右中(这里中是包括中间元素的子序列)最大和。对左右分别深度递归,三者中最大值即为当前最大子序列和。 +3. 动态规划,找到状态转移方程,求到当前位置最大和。 + +## 代码 (`Java/Python3/Javascript`) +#### 解法二 - 前缀和 + 暴力 +*Java code* +```java +class MaximumSubarrayPrefixSum { + public int maxSubArray(int[] nums) { + int len = nums.length; + int maxSum = Integer.MIN_VALUE; + int sum = 0; + for (int i = 0; i < len; i++) { + sum = 0; + for (int j = i; j < len; j++) { + sum += nums[j]; + maxSum = Math.max(maxSum, sum); + } + } + return maxSum; + } +} +``` +*Python3 code* `(TLE)` +```python +import sys +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + n = len(nums) + maxSum = -sys.maxsize + sum = 0 + for i in range(n): + sum = 0 + for j in range(i, n): + sum += nums[j] + maxSum = max(maxSum, sum) + + return maxSum +``` + +*Javascript code* from [**@lucifer**](https://github.com/azl397985856) + +```javascript +function LSS(list) { + const len = list.length; + let max = -Number.MAX_VALUE; + let sum = 0; + for (let i = 0; i < len; i++) { + sum = 0; + for (let j = i; j < len; j++) { + sum += list[j]; + if (sum > max) { + max = sum; + } + } + } + + return max; +} +``` +#### 解法三 - 优化前缀和 +*Java code* +```java +class MaxSumSubarray { + public int maxSubArray3(int[] nums) { + int maxSum = nums[0]; + int sum = 0; + int minSum = 0; + for (int num : nums) { + // prefix Sum + sum += num; + // update maxSum + maxSum = Math.max(maxSum, sum - minSum); + // update minSum + minSum = Math.min(minSum, sum); + } + return maxSum; + } +} +``` +*Python3 code* +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + n = len(nums) + maxSum = nums[0] + minSum = sum = 0 + for i in range(n): + sum += nums[i] + maxSum = max(maxSum, sum - minSum) + minSum = min(minSum, sum) + + return maxSum +``` + +*Javascript code* from [**@lucifer**](https://github.com/azl397985856) +```javascript +function LSS(list) { + const len = list.length; + let max = list[0]; + let min = 0; + let sum = 0; + for (let i = 0; i < len; i++) { + sum += list[i]; + if (sum - min > max) max = sum - min; + if (sum < min) { + min = sum; + } + } + + return max; +} +``` + +#### 解法四 - 分治法 + +*Java code* +```java +class MaximumSubarrayDivideConquer { + public int maxSubArrayDividConquer(int[] nums) { + if (nums == null || nums.length == 0) return 0; + return helper(nums, 0, nums.length - 1); + } + private int helper(int[] nums, int l, int r) { + if (l > r) return Integer.MIN_VALUE; + int mid = (l + r) >>> 1; + int left = helper(nums, l, mid - 1); + int right = helper(nums, mid + 1, r); + int leftMaxSum = 0; + int sum = 0; + // left surfix maxSum start from index mid - 1 to l + for (int i = mid - 1; i >= l; i--) { + sum += nums[i]; + leftMaxSum = Math.max(leftMaxSum, sum); + } + int rightMaxSum = 0; + sum = 0; + // right prefix maxSum start from index mid + 1 to r + for (int i = mid + 1; i <= r; i++) { + sum += nums[i]; + rightMaxSum = Math.max(sum, rightMaxSum); + } + // max(left, right, crossSum) + return Math.max(leftMaxSum + rightMaxSum + nums[mid], Math.max(left, right)); + } +} +``` + +*Python3 code* + +```python +import sys +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + return self.helper(nums, 0, len(nums) - 1) + def helper(self, nums, l, r): + if l > r: + return -sys.maxsize + mid = (l + r) // 2 + left = self.helper(nums, l, mid - 1) + right = self.helper(nums, mid + 1, r) + left_suffix_max_sum = right_prefix_max_sum = 0 + sum = 0 + for i in reversed(range(l, mid)): + sum += nums[i] + left_suffix_max_sum = max(left_suffix_max_sum, sum) + sum = 0 + for i in range(mid + 1, r + 1): + sum += nums[i] + right_prefix_max_sum = max(right_prefix_max_sum, sum) + cross_max_sum = left_suffix_max_sum + right_prefix_max_sum + nums[mid] + return max(cross_max_sum, left, right) +``` + +*Javascript code* from [**@lucifer**](https://github.com/azl397985856) + +```javascript +function helper(list, m, n) { + if (m === n) return list[m]; + let sum = 0; + let lmax = -Number.MAX_VALUE; + let rmax = -Number.MAX_VALUE; + const mid = ((n - m) >> 1) + m; + const l = helper(list, m, mid); + const r = helper(list, mid + 1, n); + for (let i = mid; i >= m; i--) { + sum += list[i]; + if (sum > lmax) lmax = sum; + } + + sum = 0; + + for (let i = mid + 1; i <= n; i++) { + sum += list[i]; + if (sum > rmax) rmax = sum; + } + + return Math.max(l, r, lmax + rmax); +} + +function LSS(list) { + return helper(list, 0, list.length - 1); +} +``` + +#### 解法五 - 动态规划 + +*Java code* + ```java +class MaximumSubarrayDP { + public int maxSubArray(int[] nums) { + int currMaxSum = nums[0]; + int maxSum = nums[0]; + for (int i = 1; i < nums.length; i++) { + currMaxSum = Math.max(currMaxSum + nums[i], nums[i]); + maxSum = Math.max(maxSum, currMaxSum); + } + return maxSum; + } +} +``` + +*Python3 code* +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + n = len(nums) + max_sum_ending_curr_index = max_sum = nums[0] + for i in range(1, n): + max_sum_ending_curr_index = max(max_sum_ending_curr_index + nums[i], nums[i]) + max_sum = max(max_sum_ending_curr_index, max_sum) + + return max_sum +``` + +*Javascript code* from [**@lucifer**](https://github.com/azl397985856) + +```javascript +function LSS(list) { + const len = list.length; + let max = list[0]; + for (let i = 1; i < len; i++) { + list[i] = Math.max(0, list[i - 1]) + list[i]; + if (list[i] > max) max = list[i]; + } + + return max; +} +``` + +## 扩展 +- 如果数组是二维数组,求最大子数组的和? +- 如果要求最大子序列的乘积? + +## 相似题 +- [Maximum Product Subarray](https://leetcode.com/problems/maximum-product-subarray/) +- [Longest Turbulent Subarray](https://leetcode.com/problems/longest-turbulent-subarray/) diff --git a/problems/53.maximum-sum-subarray-en.md b/problems/53.maximum-sum-subarray-en.md new file mode 100644 index 0000000..965f140 --- /dev/null +++ b/problems/53.maximum-sum-subarray-en.md @@ -0,0 +1,389 @@ +## Problem +https://leetcode.com/problems/maximum-subarray/ + +## Problem Description +``` +Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum. + +Example: + +Input: [-2,1,-3,4,-1,2,1,-5,4], +Output: 6 +Explanation: [4,-1,2,1] has the largest sum = 6. +Follow up: + +If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle. +``` + +## Solution + +Below will explain 4 different approaches to solve this problem. + +#### Solution #1 - Brute Force +Usually start from brute force when you don't have any idea, then step by step to optimize your solution. + +**Brute Force:**(TLE) + +Subarray sum, we then need to know subarray range [l, r], 2 `for` loop, list all possible subarrays, then 1 `for` loop to calculate current subarray sum, +using a global variable to keep track `maxSum`. This approach has very bad performance,time complexity is `O(n^3)`. + +#### Complexity Analysis +- *Time Complexity:* `O(n^3) - n array length` +- *Space Complexity:* `O(1)` + +#### Solution #2 - PrefixSum + Brute Force + +**Optimal brute force:** (AC) + +With brute force approach, we can precalculate prefixSum, so that no need to calculate subarray sum every time, time complexity can reduce to `O(n^2)` + +calculate prefixSum, for giving subarray range `[l,r]`, +current subarray sum: `subarraySum = prefixSum[r] - prefixSum[l - 1];` +global variable `maxSum`, compare every possible subarray sum to record max sum, `maxSum = max(maxSum, subarraySum)`. + + +#### Complexity Analysis +- *Time Complexity:* `O(n^2) - n array length` +- *Space Complexity:* `O(n) - prefixSum array length n` + +>If update original input array to represent prefix sum, then space will reduce to `O(1)` + +With this optimization, the time complexity is still too high, can we come up better optimization approach. + +yes, optimized prefix sum + +#### Solution #3 - optimized prefix sum - from [**@lucifer**](https://github.com/azl397985856) + +we define` S(i)` ,use to calculate sum from range `[0, i]`。 + +then `S(j) - S(i - 1)` is sum of range `[i, j]`. + +Here, we can get all `S[i] , (i = 0,1,2....,n-1)` with one loop array. +at the same time, we get min sum from `S[k], (k = 0,1,i-1)`, then + +`maxSum = max(maxSum, S[i] - minSum)`. + +Here we maintain two variables `minSum`, `maxSum`. + +#### Complexity Analysis +- *Time Complexity:* `O(n) - n array length` +- *Space Complexity:* `O(1)` + + +#### Solution #4 - [Divide and Conquer](https://www.wikiwand.com/en/Divide-and-conquer_algorithm) + +We partition array `nums` into two smaller arrays (`left` and `right`) from middle index `m`, + +Then we have two arrays: +- `left = nums[0]...nums[m - 1]` +- `right = nums[m + 1]...nums[n-1]` + +The maximum subarray sum can be either one of below three maximum sum: +1. Consider middle element `nums[m]`, Cross left and right subarray, the maximum sum is sum of + +maximum left array suffix sum - leftMaxSum, maximum right array prefix sum - rightMaxSum and middle element - nums[m] +-> `crossMaxSum = leftMaxSum + rightMaxSum + nums[m]` + +2. Dont' contain middle element `nums[m]`, maxSum is in `left`, left do recursive return max. +3. Don't contain middle element `nums[m]`, maxSum is in `right`, right do recursive return max. + +The maximum sum is `max(left, right, crossMaxSum)` + +For example, `nums=[-2,1,-3,4,-1,2,1,-5,4]` + +![maximum subarray sum divide conquer](../assets/problems/53.maximum-sum-subarray-divideconquer.png) + + +#### Complexity Analysis +- *Time Complexity:* `O(nlogn) - n input array length` +- *Space Complexity:* `O(1)` + +#### Solution #5 - [Dynamic Programing](https://www.wikiwand.com/zh-hans/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92) +Key points of DP is to find DP formula and initial state. Assume we have + +`dp[i] - maximum sum of subarray that ends at index i` + +DP formula: +`dp[i] = max(dp[i - 1] + nums[i], nums[i])` + +Initial state:`dp[0] = nums[0]` + +From above DP formula, notice only need to access its previous element at each step. In this case, we can use two variables, + +`currMaxSum - maximum sum of subarray that must end with current index i`. + +`maxSum - global maximum subarray sum` + +- `currMaxSum = max(currMaxSum + nums[i], nums[i]` +- `maxSum = max(currMaxSum, maxSum)` + +As below pic: +![maximum subarray sum dp](../assets/problems/53.maximum-sum-subarray-dp.png) + +#### Complexity Analysis +- *Time Complexity:* `O(n) - n array length` +- *Space Complexity:* `O(1)` + + +## Key Points +1. Brute force, list all possible subarray, calculate sum for each subarray (use prefixSum to optimize), return max. +2. Divide and Conquer, from middle index, divide array into left and right part. +Recursively get left maximum sum and right maximum sum, and include middle element maximum sum. +`return max(leftMaxSum, rightMaxSum, and crossMaxSum)`. +3. Dynamic Programming, find DP formula and initial state, and identify initial value, return maximum sum subarray。 + +## Code (`Java/Python3/Javascript`) +#### Solution #2 - PrefixSum + Brute Force +*Java code* +```java +class MaximumSubarrayPrefixSum { + public int maxSubArray(int[] nums) { + int len = nums.length; + int maxSum = Integer.MIN_VALUE; + int sum = 0; + for (int i = 0; i < len; i++) { + sum = 0; + for (int j = i; j < len; j++) { + sum += nums[j]; + maxSum = Math.max(maxSum, sum); + } + } + return maxSum; + } +} +``` +*Python3 code* `(TLE)` +```python +import sys +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + n = len(nums) + maxSum = -sys.maxsize + sum = 0 + for i in range(n): + sum = 0 + for j in range(i, n): + sum += nums[j] + maxSum = max(maxSum, sum) + + return maxSum +``` + +*Javascript code* from [**@lucifer**](https://github.com/azl397985856) + +```javascript +function LSS(list) { + const len = list.length; + let max = -Number.MAX_VALUE; + let sum = 0; + for (let i = 0; i < len; i++) { + sum = 0; + for (let j = i; j < len; j++) { + sum += list[j]; + if (sum > max) { + max = sum; + } + } + } + + return max; +} +``` + +#### Solution #3 + +*Java code* +```java +class MaxSumSubarray { + public int maxSubArray3(int[] nums) { + int maxSum = nums[0]; + int sum = 0; + int minSum = 0; + for (int num : nums) { + // prefix Sum + sum += num; + // update maxSum + maxSum = Math.max(maxSum, sum - minSum); + // update minSum + minSum = Math.min(minSum, sum); + } + return maxSum; + } +} +``` +*Python3 code* +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + n = len(nums) + maxSum = nums[0] + minSum = sum = 0 + for i in range(n): + sum += nums[i] + maxSum = max(maxSum, sum - minSum) + minSum = min(minSum, sum) + + return maxSum +``` + +*Javascript code* from [**@lucifer**](https://github.com/azl397985856) +```javascript +function LSS(list) { + const len = list.length; + let max = list[0]; + let min = 0; + let sum = 0; + for (let i = 0; i < len; i++) { + sum += list[i]; + if (sum - min > max) max = sum - min; + if (sum < min) { + min = sum; + } + } + + return max; +} +``` + +#### Solution #4 - Divide and Conquer + +*Java code* +```java +class MaximumSubarrayDivideConquer { + public int maxSubArrayDividConquer(int[] nums) { + if (nums == null || nums.length == 0) return 0; + return helper(nums, 0, nums.length - 1); + } + private int helper(int[] nums, int l, int r) { + if (l > r) return Integer.MIN_VALUE; + int mid = (l + r) >>> 1; + int left = helper(nums, l, mid - 1); + int right = helper(nums, mid + 1, r); + int leftMaxSum = 0; + int sum = 0; + // left surfix maxSum start from index mid - 1 to l + for (int i = mid - 1; i >= l; i--) { + sum += nums[i]; + leftMaxSum = Math.max(leftMaxSum, sum); + } + int rightMaxSum = 0; + sum = 0; + // right prefix maxSum start from index mid + 1 to r + for (int i = mid + 1; i <= r; i++) { + sum += nums[i]; + rightMaxSum = Math.max(sum, rightMaxSum); + } + // max(left, right, crossSum) + return Math.max(leftMaxSum + rightMaxSum + nums[mid], Math.max(left, right)); + } +} +``` + +*Python3 code* + +```python +import sys +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + return self.helper(nums, 0, len(nums) - 1) + def helper(self, nums, l, r): + if l > r: + return -sys.maxsize + mid = (l + r) // 2 + left = self.helper(nums, l, mid - 1) + right = self.helper(nums, mid + 1, r) + left_suffix_max_sum = right_prefix_max_sum = 0 + sum = 0 + for i in reversed(range(l, mid)): + sum += nums[i] + left_suffix_max_sum = max(left_suffix_max_sum, sum) + sum = 0 + for i in range(mid + 1, r + 1): + sum += nums[i] + right_prefix_max_sum = max(right_prefix_max_sum, sum) + cross_max_sum = left_suffix_max_sum + right_prefix_max_sum + nums[mid] + return max(cross_max_sum, left, right) +``` + +*Javascript code* from [**@lucifer**](https://github.com/azl397985856) + +```javascript +function helper(list, m, n) { + if (m === n) return list[m]; + let sum = 0; + let lmax = -Number.MAX_VALUE; + let rmax = -Number.MAX_VALUE; + const mid = ((n - m) >> 1) + m; + const l = helper(list, m, mid); + const r = helper(list, mid + 1, n); + for (let i = mid; i >= m; i--) { + sum += list[i]; + if (sum > lmax) lmax = sum; + } + + sum = 0; + + for (let i = mid + 1; i <= n; i++) { + sum += list[i]; + if (sum > rmax) rmax = sum; + } + + return Math.max(l, r, lmax + rmax); +} + +function LSS(list) { + return helper(list, 0, list.length - 1); +} +``` +#### Solution #5 - Dynamic Programming + + *Java code* + ```java +class MaximumSubarrayDP { + public int maxSubArray(int[] nums) { + int currMaxSum = nums[0]; + int maxSum = nums[0]; + for (int i = 1; i < nums.length; i++) { + currMaxSum = Math.max(currMaxSum + nums[i], nums[i]); + maxSum = Math.max(maxSum, currMaxSum); + } + return maxSum; + } +} +``` + +*Python3 code* +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + n = len(nums) + max_sum_ending_curr_index = max_sum = nums[0] + for i in range(1, n): + max_sum_ending_curr_index = max(max_sum_ending_curr_index + nums[i], nums[i]) + max_sum = max(max_sum_ending_curr_index, max_sum) + + return max_sum +``` + +*Javascript code* from [**@lucifer**](https://github.com/azl397985856) + +```javascript +function LSS(list) { + const len = list.length; + let max = list[0]; + for (let i = 1; i < len; i++) { + list[i] = Math.max(0, list[i - 1]) + list[i]; + if (list[i] > max) max = list[i]; + } + + return max; +} +``` + +## Follow Up +- When given M*N matrix, how to calculate maximum submatrix sum? +- When given array, return maximum product subarray? compare with maximum sum subarray, what is the difference? + +## Similar Questions +- [Maximum Product Subarray](https://leetcode.com/problems/maximum-product-subarray/) +- [Longest Turbulent Subarray](https://leetcode.com/problems/longest-turbulent-subarray/) diff --git a/problems/547.friend-circles-en.md b/problems/547.friend-circles-en.md new file mode 100644 index 0000000..47e6377 --- /dev/null +++ b/problems/547.friend-circles-en.md @@ -0,0 +1,231 @@ +## Problem +https://leetcode.com/problems/friend-circles/ + +## Problem Description +``` +There are N students in a class. Some of them are friends, while some are not. Their friendship is transitive in nature. +For example, if A is a direct friend of B, and B is a direct friend of C, then A is an indirect friend of C. +And we defined a friend circle is a group of students who are direct or indirect friends. + +Given a N*N matrix M representing the friend relationship between students in the class. +If M[i][j] = 1, then the ith and jth students are direct friends with each other, otherwise not. +And you have to output the total number of friend circles among all the students. + +Example 1: + +Input: +[[1,1,0], + [1,1,0], + [0,0,1]] +Output: 2 +Explanation:The 0th and 1st students are direct friends, so they are in a friend circle. +The 2nd student himself is in a friend circle. So return 2. +Example 2: + +Input: +[[1,1,0], + [1,1,1], + [0,1,1]] +Output: 1 +Explanation:The 0th and 1st students are direct friends, the 1st and 2nd students are direct friends, +so the 0th and 2nd students are indirect friends. All of them are in the same friend circle, so return 1. +Note: + +N is in range [1,200]. +M[i][i] = 1 for all students. +If M[i][j] = 1, then M[j][i] = 1. +``` + +## Solution + +We can view a given matrix as [Adjacency Matrix](https://www.wikiwand.com/en/Adjacency_matrix) of a graph. In this case, +this problem become to find number of connected components in a undirected graph. + +For example, how to transfer Adjacency Matrix into a graph problem. As below pic: + +![adjacency matrix](../assets/problems/547.friend-circle-1.png) + +Connected components in a graph problem usually can be solved using *DFS*, *BFS*, *Union-Find*. + +Below we will explain details on each approach. + +#### Approach #1. DFS +1. Do DFS starting from every node, use `visited` array to mark visited node. +2. For each node DFS, visit all its directly connected nodes. +3. For each node DFS, DFS search will search all connected nodes, thus we count one DFS as one connected component. + +as below pic show *DFS* traverse process: + +![friend circle DFS](../assets/problems/547.friend-circle-dfs.png) + +#### Complexity Analysis +- *Time Complexity:* `O(n*n) - n is the number of students, traverse n*n matrix` +- *Space Complexity:* `O(n) - visited array of size n` + +#### Approach #2. BFS (Level traverse) + +1. Start from one node, visit all its directly connected nodes, or visit all nodes in the same level. +2. Use `visited` array to mark already visited nodes. +3. Increment count when start with a new node. + +as below pic show *BFS* (Level traverse) traverse process: + +![friend circle BFS](../assets/problems/547.friend-circle-bfs.png) + +#### Complexity Analysis +- *Time Complexity:* `O(n*n) - n is the number of students, traverse n*n matrix` +- *Space Complexity:* `O(n) - queue and visited array of size n` + +#### Approach #3. [Union-Find](https://snowan.github.io/post/union-find/) + +Determine number of connected components, Union Find is good algorithm to use. + +Use `parent` array, for every node, all its directly connected nodes will be `union`, +so that two connected nodes have the same parent. After traversal all nodes, we just need to calculate +the number of different values in `parent` array. + +For each union, total count minus 1, after traversal and union all connected nodes, simply +return counts. + +Here use **weighted-union-find** to reduce `union` and `find` methods operation time. + +To know more details and implementations, see further reading lists. + +as below Union-Find approach process: + +![friend circle union-find](../assets/problems/547.friend-circle-uf.png) + +> **Note:** here using weighted-union-find to avoid Union and Find take `O(n)` in the worst case. + +#### Complexity Analysis +- *Time Complexity:* `O(n*n*log(n) - traverse n*n matrix, weighted-union-find, union and find takes log(n) time complexity.` +- *Space Complexity:* `O(n) - parent and rank array of size n` + +## Key Points +1. Transform Adjacency matrix into Graph +2. Notice that it actually is to find number of connected components problem. +3. Connected components problem approaches (DFS, BFS, Union-Find). + +## Code (`Java`) +*Java code* - **DFS** +```java +class FindCirclesDFS { + public int findCircleNumDFS(int[][] M) { + if (M == null || M.length == 0 || M[0].length == 0) return 0; + int n = M.length; + int numCircles = 0; + boolean[] visited = new boolean[n]; + for (int i = 0; i < n; i++) { + if (!visited[i]) { + dfs(M, i, visited, n); + numCircles++; + } + } + return numCircles; + } + + private void dfs(int[][] M, int i, boolean[] visited, int n) { + for (int j = 0; j < n; j++) { + if (M[i][j] == 1 && !visited[j]) { + visited[j] = true; + dfs(M, j, visited, n); + } + } + } +} +``` + +*Java code* - **BFS** +```java +class FindCircleBFS { + public int findCircleNumBFS(int[][] M) { + if (M == null || M.length == 0) return 0; + int numCircle = 0; + int n = M.length; + boolean[] visited = new boolean[n]; + Queue queue = new LinkedList<>(); + for (int i = 0; i < n; i++) { + // already visited, skip + if (visited[i]) continue; + queue.add(i); + while (!queue.isEmpty()) { + int curr = queue.poll(); + visited[curr] = true; + for (int j = 0; j < n; j++) { + if (M[curr][j] == 1 && !visited[j]) { + queue.add(j); + } + } + } + numCircle++; + } + return numCircle; + } +} +``` + +*Java code* - **Union-Find** +```java +class FindCircleUF { + public int findCircleNumUF(int[][] M) { + if (M == null || M.length == 0 || M[0].length == 0) return 0; + int n = M.length; + UnionFind uf = new UnionFind(n); + for (int i = 0; i < n - 1; i++) { + for (int j = i + 1; j < n; j++) { + // union friends + if (M[i][j] == 1) { + uf.union(i, j); + } + } + } + return uf.count; + } +} + +class UnionFind { + int count; + int[] parent; + int[] rank; + + public UnionFind(int n) { + count = n; + parent = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + public int find(int a) { + return parent[a] == a ? a : find(parent[a]); + } + + public void union(int a, int b) { + int rootA = find(a); + int rootB = find(b); + if (rootA == rootB) return; + if (rank[rootA] <= rank[rootB]) { + parent[rootA] = rootB; + rank[rootB] += rank[rootA]; + } else { + parent[rootB] = rootA; + rank[rootA] += rank[rootB]; + } + count--; + } + + public int count() { + return count; + } +} +``` + +## References (Further reading) +1. [Adjacency Matrix Wiki](https://www.wikiwand.com/en/Adjacency_matrix) +2. [Union-Find Wiki](https://www.wikiwand.com/en/Disjoint-set_data_structure) +3. [Algorighms 4 union-find](https://www.cs.princeton.edu/~rs/AlgsDS07/01UnionFind.pdf) + +## Similar Problems +1. [323. Number of Connected Components in an Undirected Graph](https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/) +2. [1101. The Earliest Moment When Everyone Become Friends](https://leetcode.com/problems/the-earliest-moment-when-everyone-become-friends/) diff --git a/problems/547.friend-circles.md b/problems/547.friend-circles.md new file mode 100644 index 0000000..a334ee5 --- /dev/null +++ b/problems/547.friend-circles.md @@ -0,0 +1,86 @@ +## 题目地址(547. 朋友圈) + +https://leetcode-cn.com/problems/friend-circles/ + +## 题目描述 + +班上有  N  名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B  的朋友,B 是 C  的朋友,那么我们可以认为 A 也是 C  的朋友。所谓的朋友圈,是指所有朋友的集合。 + +给定一个  N \* N  的矩阵  M,表示班级中学生之间的朋友关系。如果 M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。 + +示例 1: + +输入: +[[1,1,0], +[1,1,0], +[0,0,1]] +输出: 2 +说明:已知学生 0 和学生 1 互为朋友,他们在一个朋友圈。 +第 2 个学生自己在一个朋友圈。所以返回 2。 +示例 2: + +输入: +[[1,1,0], +[1,1,1], +[0,1,1]] +输出: 1 +说明:已知学生 0 和学生 1 互为朋友,学生 1 和学生 2 互为朋友,所以学生 0 和学生 2 也是朋友,所以他们三个在一个朋友圈,返回 1。 +注意: + +N 在[1,200]的范围内。 +对于所有学生,有 M[i][i] = 1。 +如果有 M[i][j] = 1,则有 M[j][i] = 1。 + +## 思路 + +并查集有一个功能是可以轻松计算出连通分量,然而本题的朋友圈的个数,本质上就是连通分量的个数,因此用并查集可以完美解决。 + +为了简单更加清晰,我将并查集模板代码单尽量独拿出来。 + +## 代码 + +`find`, `union`, `connected` 都是典型的模板方法。 懂的同学可能也发现了,我没有做路径压缩,这直接导致 find union connected 的时间复杂度最差的情况退化到 $O(N)$。 + +当然优化也不难,我们只需要给每一个顶层元素设置一个 size 用来表示连通分量的大小,这样 union 的时候我们将小的拼接到大的上即可。 另外 find 的时候我们甚至可以路径压缩,将树高限定到常数,这样时间复杂度可以降低到 $O(1)$。 + +```python +class UF: + parent = {} + cnt = 0 + def __init__(self, M): + n = len(M) + for i in range(n): + self.parent[i] = i + self.cnt += 1 + + def find(self, x): + while x != self.parent[x]: + x = self.parent[x] + return x + def union(self, p, q): + if self.connected(p, q): return + self.parent[self.find(p)] = self.find(q) + self.cnt -= 1 + def connected(self, p, q): + return self.find(p) == self.find(q) + +class Solution: + def findCircleNum(self, M: List[List[int]]) -> int: + n = len(M) + uf = UF(M) + for i in range(n): + for j in range(i): + if M[i][j] == 1: + uf.union(i, j) + return uf.cnt + +``` + +**复杂度分析** + +- 时间复杂度:平均 $O(logN)$,最坏的情况是 $O(N)$ +- 空间复杂度:我们使用了 parent, 因此空间复杂度为 $O(N)$ + +欢迎关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) diff --git a/problems/55.jump-game.md b/problems/55.jump-game.md new file mode 100644 index 0000000..db822c3 --- /dev/null +++ b/problems/55.jump-game.md @@ -0,0 +1,78 @@ + +## 题目地址 +https://leetcode.com/problems/jump-game/description/ + +## 题目描述 +``` +Given an array of non-negative integers, you are initially positioned at the first index of the array. + +Each element in the array represents your maximum jump length at that position. + +Determine if you are able to reach the last index. + +Example 1: + +Input: [2,3,1,1,4] +Output: true +Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index. +Example 2: + +Input: [3,2,1,0,4] +Output: false +Explanation: You will always arrive at index 3 no matter what. Its maximum + jump length is 0, which makes it impossible to reach the last index. + +``` + +## 思路 + +这道题目是一道典型的`贪心`类型题目。思路就是用一个变量记录当前能够到达的最大的索引,我们逐个遍历数组中的元素去更新这个索引。变量完成判断这个索引是否大于数组下表即可。 +## 关键点解析 + +- 建模 (记录和更新当前位置能够到达的最大的索引即可) + +## 代码 + +* 语言支持: Javascript,Python3 + +```js +/** + * @param {number[]} nums + * @return {boolean} + */ +var canJump = function(nums) { + let max = 0; // 能够走到的数组下标 + + for(let i = 0; i < nums.length; i++) { + if (max < i) return false; // 当前这一步都走不到,后面更走不到了 + max = Math.max(nums[i] + i, max); + } + + return max >= nums.length - 1 +}; + +``` +Python3 Code: +```Python +class Solution: + def canJump(self, nums: List[int]) -> bool: + """思路同上""" + _max = 0 + _len = len(nums) + for i in range(_len-1): + if _max < i: + return False + _max = max(_max, nums[i] + i) + # 下面这个判断可有可无,但提交的时候数据会好看点 + if _max >= _len - 1: + return True + return _max >= _len - 1 +``` + +***复杂度分析*** +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(1)$ + +更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经30K star啦。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 diff --git a/problems/56.merge-intervals.md b/problems/56.merge-intervals.md new file mode 100644 index 0000000..9acfdab --- /dev/null +++ b/problems/56.merge-intervals.md @@ -0,0 +1,100 @@ + +## 题目地址 +https://leetcode.com/problems/merge-intervals/description/ + +## 题目描述 +``` +Given a collection of intervals, merge all overlapping intervals. + +Example 1: + +Input: [[1,3],[2,6],[8,10],[15,18]] +Output: [[1,6],[8,10],[15,18]] +Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6]. +Example 2: + +Input: [[1,4],[4,5]] +Output: [[1,5]] +Explanation: Intervals [1,4] and [4,5] are considered overlapping. +NOTE: input types have been changed on April 15, 2019. Please reset to default code definition to get new method signature. + +``` + +## 思路 + +- 先对数组进行排序,排序的依据就是每一项的第一个元素的大小。 +- 然后我们对数组进行遍历,遍历的时候两两运算(具体运算逻辑见下) +- 判断是否相交,如果不相交,则跳过 +- 如果相交,则合并两项 +## 关键点解析 + +- 对数组进行排序简化操作 +- 如果不排序,需要借助一些hack,这里不介绍了 + +## 代码 + +* 语言支持: Javascript,Python3 + +```js + + +/* + * @lc app=leetcode id=56 lang=javascript + * + * [56] Merge Intervals + */ +/** + * @param {number[][]} intervals + * @return {number[][]} + */ + +function intersected(a, b) { + if (a[0] > b[1] || a[1] < b[0]) return false; + return true; +} + +function mergeTwo(a, b) { + return [Math.min(a[0], b[0]), Math.max(a[1], b[1])]; +} +var merge = function(intervals) { + // 这种算法需要先排序 + intervals.sort((a, b) => a[0] - b[0]); + for (let i = 0; i < intervals.length - 1; i++) { + const cur = intervals[i]; + const next = intervals[i + 1]; + + if (intersected(cur, next)) { + intervals[i] = undefined; + intervals[i + 1] = mergeTwo(cur, next); + } + } + return intervals.filter(q => q); +}; +``` +Python3 Code: +```Python +class Solution: + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + """先排序,后合并""" + if len(intervals) <= 1: + return intervals + + # 排序 + def get_first(a_list): + return a_list[0] + intervals.sort(key=get_first) + + # 合并 + res = [intervals[0]] + for i in range(1, len(intervals)): + if intervals[i][0] <= res[-1][1]: + res[-1] = [res[-1][0], max(res[-1][1], intervals[i][1])] + else: + res.append(intervals[i]) + + return res +``` + +***复杂度分析*** +- 时间复杂度:由于采用了排序,因此复杂度大概为 $O(NlogN)$ +- 空间复杂度:$O(N)$ diff --git a/problems/560.subarray-sum-equals-k.en.md b/problems/560.subarray-sum-equals-k.en.md new file mode 100644 index 0000000..c368848 --- /dev/null +++ b/problems/560.subarray-sum-equals-k.en.md @@ -0,0 +1,146 @@ +## Problem + +https://leetcode.com/problems/subarray-sum-equals-k/description/ + +## Problem Description + +``` +Given an array of integers and an integer k, you need to find the total number of continuous subarrays whose sum equals to k. + +Example 1: +Input:nums = [1,1,1], k = 2 +Output: 2 +Note: +The length of the array is in range [1, 20,000]. +The range of numbers in the array is [-1000, 1000] and the range of the integer k is [-1e7, 1e7]. +``` + +## Solution + +The simplest method is `Brute-force`. Consider every possible subarray, find the sum of the elements of each of those subarrays and check for the equality of the sum with `k`. Whenever the sum equals `k`, we increment the `count`. Time Complexity is O(n^2). Implementation is as followed. + +```py +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt, n = 0, len(nums) + for i in range(n): + for j in range(i, n): + if (sum(nums[i:j + 1]) == k): cnt += 1 + return cnt +``` + +If we implement the `sum()` method on our own, we get the time of complexity O(n^3). + +```py +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt, n = 0, len(nums) + for i in range(n): + for j in range(i, n): + sum = 0 + for x in range(i, j + 1): + sum += nums[x] + if (sum == k): cnt += 1 + return cnt +``` + +At first glance I think "maybe it can be solved by using the sliding window technique". However, I give that thought up when I find out that the given array may contain negative numbers, which makes it more complicated to expand or narrow the range of the sliding window. Then I think about using a prefix sum array, with which we can obtain the sum of the elements between every two indices by subtracting the prefix sum corresponding to the two indices. It sounds feasible, so I implement it as followed. + +```py +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt, n = 0, len(nums) + pre = [0] * (n + 1) + for i in range(1, n + 1): + pre[i] = pre[i - 1] + nums[i - 1] + for i in range(1, n + 1): + for j in range(i, n + 1): + if (pre[j] - pre[i - 1] == k): cnt += 1 + return cnt +``` + +Actually, there is a more clever way to do this. Instead of using a prefix sum array, we use a hashmap to reduce the time complexity to O(n). + +Algorithm: + +- We make use of a hashmap to store the cumulative sum `acc` and the number of times the same sum occurs. We use `acc` as the `key` of the hashmap and the number of times the same `acc` occurs as the `value`. + +- We traverse over the given array and keep on finding the cumulative sum `acc`. Every time we encounter a new `acc` we add a new entry to the hashmap. If the same `acc` occurs, we increment the count corresponding to that `acc` in the hashmap. If `acc` equals `k`, obviously `count` should be incremented. If `acc - k` got, we should increment `account` by `hashmap[acc - k]`. + +- The idea behind this is that if the cumulative sum upto two indices is the same, the sum of the elements between those two indices is zero. So if the cumulative sum upto two indices is at a different of `k`, the sum of the elements between those indices is `k`. As `hashmap[acc - k]` keeps track of the number of times a subarray with sum `acc - k` has occured upto the current index, by doing a simple substraction `acc - (acc - k)` we can see that `hashmap[acc - k]` actually also determines the number of times a subarray with sum `k` has occured upto the current index. So we increment the `count` by `hashmap[acc - k]`. + +Here is a graph demonstrating this algorithm in the case of `nums = [1,2,3,3,0,3,4,2], k = 6`. + +![560.subarray-sum-equals-k](../assets/problems/560.subarray-sum-equals-k.jpg) + +When we are at `nums[3]`, the hashmap is as the picture shows, and `count` is 2 by this time. `[1, 2, 3]` accounts for one of the count, and `[3, 3]` accounts for another. + +The subarray `[3, 3]` is obtained from `hashmap[acc - k]`, which is `hashmap[9 - 6]`. + +## Key Points + +- Prefix sum array +- Make use of a hashmap to track cumulative sum and avoid repetitive calculation. + +## Code (`JavaScript/Python`) + +*JavaScript Code* +```js +/* + * @lc app=leetcode id=560 lang=javascript + * + * [560] Subarray Sum Equals K + */ +/** + * @param {number[]} nums + * @param {number} k + * @return {number} + */ +var subarraySum = function (nums, k) { + const hashmap = {}; + let acc = 0; + let count = 0; + + for (let i = 0; i < nums.length; i++) { + acc += nums[i]; + + if (acc === k) count++; + + if (hashmap[acc - k] !== void 0) { + count += hashmap[acc - k]; + } + + if (hashmap[acc] === void 0) { + hashmap[acc] = 1; + } else { + hashmap[acc] += 1; + } + } + + return count; +}; +``` + +*Python Cose* + +```py +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + d = {} + acc = count = 0 + for num in nums: + acc += num + if acc == k: + count += 1 + if acc - k in d: + count += d[acc-k] + if acc in d: + d[acc] += 1 + else: + d[acc] = 1 + return count +``` + +## Extension + +There is a similar but a bit more complicated problem. Link to the problem: [437.path-sum-iii](https://github.com/azl397985856/leetcode/blob/master/problems/437.path-sum-iii.md)(Chinese). diff --git a/problems/560.subarray-sum-equals-k.md b/problems/560.subarray-sum-equals-k.md new file mode 100644 index 0000000..bc4df1d --- /dev/null +++ b/problems/560.subarray-sum-equals-k.md @@ -0,0 +1,135 @@ +## 题目地址 + +https://leetcode.com/problems/subarray-sum-equals-k/description/ + +## 题目描述 + +``` +Given an array of integers and an integer k, you need to find the total number of continuous subarrays whose sum equals to k. + +Example 1: +Input:nums = [1,1,1], k = 2 +Output: 2 +Note: +The length of the array is in range [1, 20,000]. +The range of numbers in the array is [-1000, 1000] and the range of the integer k is [-1e7, 1e7]. + +``` + +## 思路 + +符合直觉的做法是暴力求解所有的子数组,然后分别计算和,如果等于 k,count 就+1.这种做法的时间复杂度为 O(n^2),代码如下: + +```python +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt, n = 0, len(nums) + for i in range(n): + sum = 0 + for j in range(i, n): + sum += nums[j] + if (sum == k): cnt += 1 + return cnt +``` + +实际上刚开始看到这题目的时候,我想“是否可以用滑动窗口解决?”。但是很快我就放弃了,因为看了下数组中项的取值范围有负数,这样我们扩张或者收缩窗口就比较复杂。第二个想法是前缀和,保存一个数组的前缀和,然后利用差分法得出任意区间段的和,这种想法是可行的,代码如下: + +```python +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt, n = 0, len(nums) + pre = [0] * (n + 1) + for i in range(1, n + 1): + pre[i] = pre[i - 1] + nums[i - 1] + for i in range(1, n + 1): + for j in range(i, n + 1): + if (pre[j] - pre[i - 1] == k): cnt += 1 + return cnt +``` + +这里有一种更加巧妙的方法,可以不使用前缀和数组,而是使用 hashmap 来简化时间复杂度,这种算法的时间复杂度可以达到 O(n). + +具体算法: + +- 维护一个 hashmap,hashmap 的 key 为累加值 acc,value 为累加值 acc 出现的次数。 +- 迭代数组,然后不断更新 acc 和 hashmap,如果 acc 等于 k,那么很明显应该+1. 如果 hashmap[acc - k] 存在,我们就把它加到结果中去即可。 + +语言比较难以解释,我画了一个图来演示 nums = [1,2,3,3,0,3,4,2], k = 6 的情况。 + +![560.subarray-sum-equals-k](../assets/problems/560.subarray-sum-equals-k.jpg) + +如图,当访问到 nums[3]的时候,hashmap 如图所示,这个时候 count 为 2. +其中之一是[1,2,3],这个好理解。还有一个是[3,3]. + +这个[3,3]正是我们通过 hashmap[acc - k]即 hashmap[9 - 6]得到的。 + +## 关键点解析 + +- 前缀和 +- 可以利用 hashmap 记录和的累加值来避免重复计算 + +## 代码 + +- 语言支持:JS, Python + +Javascript Code: + +```js +/* + * @lc app=leetcode id=560 lang=javascript + * + * [560] Subarray Sum Equals K + */ +/** + * @param {number[]} nums + * @param {number} k + * @return {number} + */ +var subarraySum = function (nums, k) { + const hashmap = {}; + let acc = 0; + let count = 0; + + for (let i = 0; i < nums.length; i++) { + acc += nums[i]; + + if (acc === k) count++; + + if (hashmap[acc - k] !== void 0) { + count += hashmap[acc - k]; + } + + if (hashmap[acc] === void 0) { + hashmap[acc] = 1; + } else { + hashmap[acc] += 1; + } + } + + return count; +}; +``` + +Python Code: + +```python +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + d = {} + acc = count = 0 + for num in nums: + acc += num + if acc == k: + count += 1 + if acc - k in d: + count += d[acc-k] + if acc in d: + d[acc] += 1 + else: + d[acc] = 1 + return count +``` + +## 扩展 + +这是一道类似的题目,但是会稍微复杂一点, 题目地址: [437.path-sum-iii](./437.path-sum-iii.md) diff --git a/problems/575.distribute-candies.md b/problems/575.distribute-candies.md new file mode 100644 index 0000000..7920275 --- /dev/null +++ b/problems/575.distribute-candies.md @@ -0,0 +1,71 @@ +## 题目地址 +https://leetcode.com/problems/distribute-candies/description/ + +## 题目描述 + +``` +Given an integer array with even length, where different numbers in this array represent different kinds of candies. Each number means one candy of the corresponding kind. You need to distribute these candies equally in number to brother and sister. Return the maximum number of kinds of candies the sister could gain. +Example 1: +Input: candies = [1,1,2,2,3,3] +Output: 3 +Explanation: +There are three different kinds of candies (1, 2 and 3), and two candies for each kind. +Optimal distribution: The sister has candies [1,2,3] and the brother has candies [1,2,3], too. +The sister has three different kinds of candies. +Example 2: +Input: candies = [1,1,2,3] +Output: 2 +Explanation: For example, the sister has candies [2,3] and the brother has candies [1,1]. +The sister has two different kinds of candies, the brother has only one kind of candies. +Note: + +The length of the given array is in range [2, 10,000], and will be even. +The number in given array is in range [-100,000, 100,000]. +``` + +## 思路 +由于糖果是偶数,并且我们只需要做到两个人糖果数量一样即可。 + +考虑两种情况: + +![575.distribute-candies](../assets/problems/575.distribute-candies.png) + +- 如果糖果种类大于n / 2(糖果种类数为n),妹妹最多可以获得的糖果种类应该是`n / 2`(因为妹妹只有n / 2个糖). +- 糖果种类数小于n / 2, 妹妹能够得到的糖果种类可以是糖果的种类数(糖果种类本身就这么多). + +因此我们发现,妹妹能够获得的糖果种类的制约因素其实是糖果种类数。 + +## 关键点解析 + +- 这是一道逻辑题目,因此如果逻辑分析清楚了,代码是自然而然的 + + +## 代码 + +* 语言支持:JS, Python + +Javascript Code: + +```js +/* + * @lc app=leetcode id=575 lang=javascript + * + * [575] Distribute Candies + */ +/** + * @param {number[]} candies + * @return {number} + */ +var distributeCandies = function(candies) { + const count = new Set(candies); + return Math.min(count.size, candies.length >> 1); +}; +``` + +Python Code: + +```python +class Solution: + def distributeCandies(self, candies: List[int]) -> int: + return min(len(set(candies)), len(candies) >> 1) +``` diff --git a/problems/60.permutation-sequence.md b/problems/60.permutation-sequence.md new file mode 100644 index 0000000..c6706f0 --- /dev/null +++ b/problems/60.permutation-sequence.md @@ -0,0 +1,96 @@ +## 题目地址(第 K 个排列) + +https://leetcode-cn.com/problems/permutation-sequence/description/ + +## 标签 + +- 数学 +- 回溯 +- 找规律 +- factorial + +## 公司 + +Twitter + +## 题目描述 + +``` +给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。 + +按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下: + +"123" +"132" +"213" +"231" +"312" +"321" +给定 n 和 k,返回第 k 个排列。 + +说明: + +给定 n 的范围是 [1, 9]。 +给定 k 的范围是[1, n!]。 +示例 1: + +输入: n = 3, k = 3 +输出: "213" +示例 2: + +输入: n = 4, k = 9 +输出: "2314" +``` + +## 思路 + +LeetCode 上关于排列的题目截止目前(2020-01-06)主要有三种类型: + +- 生成全排列 +- 生成下一个排列 +- 生成第 k 个排列(我们的题目就是这种) + +我们不可能求出所有的排列,然后找到第 k 个之后返回。因为排列的组合是 N!,要比 2^n 还要高很多,非常有可能超时。我们必须使用一些巧妙的方法。 + +我们以题目中的 n= 3 k = 3 为例: + +- "123" +- "132" +- "213" +- "231" +- "312" +- "321" + +可以看出 1xx,2xx 和 3xx 都有两个,如果你知道阶乘的话,实际上是 2!个。 我们想要找的是第 3 个。那么我们可以直接跳到 2 开头,我们排除了以 1 开头的排列,问题缩小了,我们将 2 加入到结果集,我们不断重复上述的逻辑,知道结果集的元素为 n 即可。 + +## 关键点解析 + +- 找规律 +- 排列组合 + +## 代码 + +- 语言支持: Python3 + +```python +import math + +class Solution: + def getPermutation(self, n: int, k: int) -> str: + res = "" + candidates = [str(i) for i in range(1, n + 1)] + + while n != 0: + facto = math.factorial(n - 1) + # i 表示前面被我们排除的组数,也就是k所在的组的下标 + # k // facto 是不行的, 比如在 k % facto == 0的情况下就会有问题 + i = math.ceil(k / facto) - 1 + # 我们把candidates[i]加入到结果集,然后将其弹出candidates(不能重复使用元素) + res += candidates[i] + candidates.pop(i) + # k 缩小了 facto * i + k -= facto * i + # 每次迭代我们实际上就处理了一个元素,n 减去 1,当n == 0 说明全部处理完成,我们退出循环 + n -= 1 + return res +``` diff --git a/problems/609.find-duplicate-file-in-system.md b/problems/609.find-duplicate-file-in-system.md new file mode 100644 index 0000000..8b3ab50 --- /dev/null +++ b/problems/609.find-duplicate-file-in-system.md @@ -0,0 +1,180 @@ + +## 题目地址 +https://leetcode.com/problems/find-duplicate-file-in-system/description/ + +## 题目描述 + +``` +Given a list of directory info including directory path, and all the files with contents in this directory, you need to find out all the groups of duplicate files in the file system in terms of their paths. + +A group of duplicate files consists of at least two files that have exactly the same content. + +A single directory info string in the input list has the following format: + +"root/d1/d2/.../dm f1.txt(f1_content) f2.txt(f2_content) ... fn.txt(fn_content)" + +It means there are n files (f1.txt, f2.txt ... fn.txt with content f1_content, f2_content ... fn_content, respectively) in directory root/d1/d2/.../dm. Note that n >= 1 and m >= 0. If m = 0, it means the directory is just the root directory. + +The output is a list of group of duplicate file paths. For each group, it contains all the file paths of the files that have the same content. A file path is a string that has the following format: + +"directory_path/file_name.txt" + +Example 1: + +Input: +["root/a 1.txt(abcd) 2.txt(efgh)", "root/c 3.txt(abcd)", "root/c/d 4.txt(efgh)", "root 4.txt(efgh)"] +Output: +[["root/a/2.txt","root/c/d/4.txt","root/4.txt"],["root/a/1.txt","root/c/3.txt"]] + + +Note: + +No order is required for the final output. +You may assume the directory name, file name and file content only has letters and digits, and the length of file content is in the range of [1,50]. +The number of files given is in the range of [1,20000]. +You may assume no files or directories share the same name in the same directory. +You may assume each given directory info represents a unique directory. Directory path and file info are separated by a single blank space. + + +Follow-up beyond contest: + +1. Imagine you are given a real file system, how will you search files? DFS or BFS? + +2. If the file content is very large (GB level), how will you modify your solution? + +3. If you can only read the file by 1kb each time, how will you modify your solution? + +4. What is the time complexity of your modified solution? What is the most time-consuming part and memory consuming part of it? How to optimize? + +5. How to make sure the duplicated files you find are not false positive? + +``` + +## 思路 +思路就是hashtable去存储,key为文件内容,value为fullfilename, +遍历一遍去填充hashtable, 最后将hashtable中的值打印出来即可。 + +> 当且仅当有重复内容,我们才打印,因此我们需要过滤一下, 类似 filter(q => q.length >= 2) +## 关键点解析 + +- hashtable + +## 代码 +```js + + +/* + * @lc app=leetcode id=609 lang=javascript + * + * [609] Find Duplicate File in System + * + * https://leetcode.com/problems/find-duplicate-file-in-system/description/ + * + * algorithms + * Medium (54.21%) + * Total Accepted: 24.1K + * Total Submissions: 44.2K + * Testcase Example: '["root/a 1.txt(abcd) 2.txt(efgh)","root/c 3.txt(abcd)","root/c/d 4.txt(efgh)","root 4.txt(efgh)"]' + * + * Given a list of directory info including directory path, and all the files + * with contents in this directory, you need to find out all the groups of + * duplicate files in the file system in terms of their paths. + * + * A group of duplicate files consists of at least two files that have exactly + * the same content. + * + * A single directory info string in the input list has the following format: + * + * "root/d1/d2/.../dm f1.txt(f1_content) f2.txt(f2_content) ... + * fn.txt(fn_content)" + * + * It means there are n files (f1.txt, f2.txt ... fn.txt with content + * f1_content, f2_content ... fn_content, respectively) in directory + * root/d1/d2/.../dm. Note that n >= 1 and m >= 0. If m = 0, it means the + * directory is just the root directory. + * + * The output is a list of group of duplicate file paths. For each group, it + * contains all the file paths of the files that have the same content. A file + * path is a string that has the following format: + * + * "directory_path/file_name.txt" + * + * Example 1: + * + * + * Input: + * ["root/a 1.txt(abcd) 2.txt(efgh)", "root/c 3.txt(abcd)", "root/c/d + * 4.txt(efgh)", "root 4.txt(efgh)"] + * Output: + * + * [["root/a/2.txt","root/c/d/4.txt","root/4.txt"],["root/a/1.txt","root/c/3.txt"]] + * + * + * + * + * Note: + * + * + * No order is required for the final output. + * You may assume the directory name, file name and file content only has + * letters and digits, and the length of file content is in the range of + * [1,50]. + * The number of files given is in the range of [1,20000]. + * You may assume no files or directories share the same name in the same + * directory. + * You may assume each given directory info represents a unique directory. + * Directory path and file info are separated by a single blank space. + * + * + * + * Follow-up beyond contest: + * + * + * Imagine you are given a real file system, how will you search files? DFS or + * BFS? + * If the file content is very large (GB level), how will you modify your + * solution? + * If you can only read the file by 1kb each time, how will you modify your + * solution? + * What is the time complexity of your modified solution? What is the most + * time-consuming part and memory consuming part of it? How to optimize? + * How to make sure the duplicated files you find are not false positive? + * + * + */ +/** + * @param {string[]} paths + * @return {string[][]} + */ +var findDuplicate = function(paths) { + const hashmap = {}; + + for (let path of paths) { + const [folder, ...files] = path.split(" "); + for (let file of files) { + const lpi = file.indexOf("("); + const rpi = file.lastIndexOf(")"); + const filename = file.slice(0, lpi); + const content = file.slice(lpi, rpi); + const fullname = `${folder}/${filename}`; + if (!hashmap[content]) hashmap[content] = []; + hashmap[content].push(fullname); + } + } + + return Object.values(hashmap).filter(q => q.length >= 2); +}; +``` + +## 扩展 +leetcode官方给的扩展我觉得就很有意思,虽然很`老套`, 这里还是列一下好了,大家可以作为思考题来思考一下。 + +1. Imagine you are given a real file system, how will you search files? DFS or BFS? + +2. If the file content is very large (GB level), how will you modify your solution? + +3. If you can only read the file by 1kb each time, how will you modify your solution? + +4. What is the time complexity of your modified solution? What is the most time-consuming part and memory consuming part of it? How to optimize? + +5. How to make sure the duplicated files you find are not false positive? diff --git a/problems/62.unique-paths.md b/problems/62.unique-paths.md new file mode 100644 index 0000000..8076e82 --- /dev/null +++ b/problems/62.unique-paths.md @@ -0,0 +1,171 @@ + +## 题目地址 +https://leetcode.com/problems/unique-paths/description/ + +## 题目描述 +``` + +A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). + +The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below). + +How many possible unique paths are there? +``` +![](https://tva1.sinaimg.cn/large/0082zybply1gca6k99jmoj30b4053mxa.jpg) + +``` +Above is a 7 x 3 grid. How many possible unique paths are there? + +Note: m and n will be at most 100. + +Example 1: + +Input: m = 3, n = 2 +Output: 3 +Explanation: +From the top-left corner, there are a total of 3 ways to reach the bottom-right corner: +1. Right -> Right -> Down +2. Right -> Down -> Right +3. Down -> Right -> Right +Example 2: + +Input: m = 7, n = 3 +Output: 28 +``` + +## 思路 + +这是一道典型的适合使用动态规划解决的题目,它和爬楼梯等都属于动态规划中最简单的题目,因此也经常会被用于面试之中。 + +读完题目你就能想到动态规划的话,建立模型并解决恐怕不是难事。其实我们很容易看出,由于机器人只能右移动和下移动, +因此第[i, j]个格子的总数应该等于[i - 1, j] + [i, j -1], 因为第[i,j]个格子一定是从左边或者上面移动过来的。 + +![](https://tva1.sinaimg.cn/large/0082zybply1gca6kj31o4j304z07gt8u.jpg) + +代码大概是: + +JS Code: + +```js + const dp = []; + for (let i = 0; i < m + 1; i++) { + dp[i] = []; + dp[i][0] = 0; + } + for (let i = 0; i < n + 1; i++) { + dp[0][i] = 0; + } + for (let i = 1; i < m + 1; i++) { + for(let j = 1; j < n + 1; j++) { + dp[i][j] = j === 1 ? 1 : dp[i - 1][j] + dp[i][j - 1]; // 转移方程 + } + } + + return dp[m][n]; + +``` + +Python Code: + +```python +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + d = [[1] * n for _ in range(m)] + + for col in range(1, m): + for row in range(1, n): + d[col][row] = d[col - 1][row] + d[col][row - 1] + + return d[m - 1][n - 1] + ``` + + **复杂度分析** + + - 时间复杂度:$O(M * N)$ + - 空间复杂度:$O(M * N)$ + +由于dp[i][j] 只依赖于左边的元素和上面的元素,因此空间复杂度可以进一步优化, 优化到O(n). + +![](https://tva1.sinaimg.cn/large/0082zybply1gca6l63ax7j30gr09w3zp.jpg) + +具体代码请查看代码区。 + + +当然你也可以使用记忆化递归的方式来进行,由于递归深度的原因,性能比上面的方法差不少: + +> 直接暴力递归的话会超时。 + +Python3 Code: +```python +class Solution: + visited = dict() + + def uniquePaths(self, m: int, n: int) -> int: + if (m, n) in self.visited: + return self.visited[(m, n)] + if m == 1 or n == 1: + return 1 + cnt = self.uniquePaths(m - 1, n) + self.uniquePaths(m, n - 1) + self.visited[(m, n)] = cnt + return cnt + ``` + +## 关键点 + +- 记忆化递归 +- 基本动态规划问题 +- 空间复杂度可以进一步优化到O(n), 这会是一个考点 +## 代码 + +代码支持JavaScript,Python3 + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=62 lang=javascript + * + * [62] Unique Paths + * + * https://leetcode.com/problems/unique-paths/description/ + */ +/** + * @param {number} m + * @param {number} n + * @return {number} + */ +var uniquePaths = function(m, n) { + const dp = Array(n).fill(1); + + for(let i = 1; i < m; i++) { + for(let j = 1; j < n; j++) { + dp[j] = dp[j] + dp[j - 1]; + } + } + + return dp[n - 1]; +}; +``` + +Python3 Code: + +```python +class Solution: + + def uniquePaths(self, m: int, n: int) -> int: + dp = [1] * n + for _ in range(1, m): + for j in range(1, n): + dp[j] += dp[j - 1] + return dp[n - 1] +``` + + **复杂度分析** + + - 时间复杂度:$O(M * N)$ + - 空间复杂度:$O(N)$ + + ## 扩展 + + 你可以做到比$O(M * N)$更快,比$O(N)$更省内存的算法么?这里有一份[资料](https://leetcode.com/articles/unique-paths/)可供参考。 + > 提示: 考虑数学 diff --git a/problems/721.accounts-merge.md b/problems/721.accounts-merge.md new file mode 100644 index 0000000..cb9ac89 --- /dev/null +++ b/problems/721.accounts-merge.md @@ -0,0 +1,78 @@ +## 题目地址(721. 账户合并) + +https://leetcode-cn.com/problems/accounts-merge/ + +## 题目描述 + +给定一个列表 accounts,每个元素 accounts[i]  是一个字符串列表,其中第一个元素 accounts[i][0]  是   名称 (name),其余元素是 emails 表示该帐户的邮箱地址。 + +现在,我们想合并这些帐户。如果两个帐户都有一些共同的邮件地址,则两个帐户必定属于同一个人。请注意,即使两个帐户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的帐户,但其所有帐户都具有相同的名称。 + +合并帐户后,按以下格式返回帐户:每个帐户的第一个元素是名称,其余元素是按顺序排列的邮箱地址。accounts 本身可以以任意顺序返回。 + +例子 1: + +Input: +accounts = [["John", "johnsmith@mail.com", "john00@mail.com"], ["John", "johnnybravo@mail.com"], ["John", "johnsmith@mail.com", "john_newyork@mail.com"], ["Mary", "mary@mail.com"]] +Output: [["John", 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'], ["John", "johnnybravo@mail.com"], ["Mary", "mary@mail.com"]] +Explanation: +第一个和第三个 John 是同一个人,因为他们有共同的电子邮件 "johnsmith@mail.com"。 +第二个 John 和 Mary 是不同的人,因为他们的电子邮件地址没有被其他帐户使用。 +我们可以以任何顺序返回这些列表,例如答案[['Mary','mary@mail.com'],['John','johnnybravo@mail.com'], +['John','john00@mail.com','john_newyork@mail.com','johnsmith@mail.com']]仍然会被接受。 + +注意: + +accounts 的长度将在[1,1000]的范围内。 +accounts[i]的长度将在[1,10]的范围内。 +accounts[i][j]的长度将在[1,30]的范围内。 + +## 思路 + +我们抛开 name 不管。 我们只根据 email 建立并查集即可。这样一个连通分量中的 email 就是一个人,我们在用一个 hashtable 记录 email 和 name 的映射,将其输出即可。 + +> 如果题目不要求我们输出 name,我们自然根本不需要 hashtable 做映射 + +## 代码 + +`find`, `union`, `connected` 都是典型的模板方法。 懂的同学可能也发现了,我没有做路径压缩,这直接导致 find union connected 的时间复杂度最差的情况退化到 $O(N)$。 + +当然优化也不难,我们只需要给每一个顶层元素设置一个 size 用来表示连通分量的大小,这样 union 的时候我们将小的拼接到大的上即可。 另外 find 的时候我们甚至可以路径压缩,将树高限定到常数,这样时间复杂度可以降低到 $O(1)$。 + +```python +class UF: + def __init__(self): + self.parent = {} + + def find(self, x): + self.parent.setdefault(x, x) + while x != self.parent[x]: + x = self.parent[x] + return x + def union(self, p, q): + self.parent[self.find(p)] = self.find(q) + + +class Solution: + def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]: + uf = UF() + email_to_name = {} + res = collections.defaultdict(list) + for account in accounts: + for i in range(1, len(account)): + email_to_name[account[i]] = account[0] + if i < len(account) - 1:uf.union(account[i], account[i + 1]) + for email in email_to_name: + res[uf.find(email)].append(email) + + return [[email_to_name[value[0]]] + sorted(value) for value in res.values()] +``` + +**复杂度分析** + +- 时间复杂度:平均 $O(logN)$,最坏的情况是 $O(N)$ +- 空间复杂度:我们使用了 parent, 因此空间复杂度为 $O(N)$ + +欢迎关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) diff --git a/problems/73.set-matrix-zeroes.md b/problems/73.set-matrix-zeroes.md new file mode 100644 index 0000000..6509cec --- /dev/null +++ b/problems/73.set-matrix-zeroes.md @@ -0,0 +1,253 @@ +## 题目地址 + +https://leetcode.com/problems/set-matrix-zeroes/description/ + +## 题目描述 + +``` +Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in-place. + +Example 1: + +Input: +[ + [1,1,1], + [1,0,1], + [1,1,1] +] +Output: +[ + [1,0,1], + [0,0,0], + [1,0,1] +] +Example 2: + +Input: +[ + [0,1,2,0], + [3,4,5,2], + [1,3,1,5] +] +Output: +[ + [0,0,0,0], + [0,4,5,0], + [0,3,1,0] +] +Follow up: + +- A straight forward solution using O(mn) space is probably a bad idea. +- A simple improvement uses O(m + n) space, but still not the best solution. +- Could you devise a constant space solution? + +``` + +## 思路 + +符合直觉的想法是,使用一个 m + n 的数组来表示每一行每一列是否”全部是 0“, +先遍历一遍去构建这样的 m + n 数组,然后根据这个 m + n 数组去修改 matrix 即可。 + +![73.set-matrix-zeroes-1](../assets/problems/73.set-matrix-zeroes-1.png) + +这样的时间复杂度 O(m \* n), 空间复杂度 O(m + n). + +代码如下: + +```js +var setZeroes = function(matrix) { + if (matrix.length === 0) return matrix; + const m = matrix.length; + const n = matrix[0].length; + const zeroes = Array(m + n).fill(false); + + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + const item = matrix[i][j]; + + if (item === 0) { + zeroes[i] = true; + zeroes[m + j] = true; + } + } + } + + for (let i = 0; i < m; i++) { + if (zeroes[i]) { + matrix[i] = Array(n).fill(0); + } + } + + for (let i = 0; i < n; i++) { + if (zeroes[m + i]) { + for (let j = 0; j < m; j++) { + matrix[j][i] = 0; + } + } + } + + return matrix; +}; +``` + +但是这道题目还有一个follow up, 要求使用O(1)的时间复杂度。因此上述的方法就不行了。 +但是我们要怎么去存取这些信息(哪一行哪一列应该全部为0)呢? + +一种思路是使用第一行第一列的数据来代替上述的zeros数组。 这样我们就不必借助额外的存储空间,空间复杂度自然就是O(1)了。 + +由于我们不能先操作第一行和第一列, 因此我们需要记录下”第一行和第一列是否全是0“这样的一个数据,最后根据这个信息去 +修改第一行和第一列。 + +具体步骤如下: + +- 记录下”第一行和第一列是否全是0“这样的一个数据 +- 遍历除了第一行和第一列之外的所有的数据,如果是0,那就更新第一行第一列中对应的元素为0 +> 你可以把第一行第一列看成我们上面那种解法使用的m + n 数组。 +- 根据第一行第一列的数据,更新matrix +- 最后根据我们最开始记录的”第一行和第一列是否全是0“去更新第一行和第一列即可 + +![73.set-matrix-zeroes-2](../assets/problems/73.set-matrix-zeroes-2.png) + + +## 关键点 +- 使用第一行和第一列来替代我们m + n 数组 +- 先记录下”第一行和第一列是否全是0“这样的一个数据,否则会因为后续对第一行第一列的更新造成数据丢失 +- 最后更新第一行第一列 +## 代码 + +* 语言支持:JS,Python3 + +```js +/* + * @lc app=leetcode id=73 lang=javascript + * + * [73] Set Matrix Zeroes + */ +/** + * @param {number[][]} matrix + * @return {void} Do not return anything, modify matrix in-place instead. + */ +var setZeroes = function(matrix) { + if (matrix.length === 0) return matrix; + const m = matrix.length; + const n = matrix[0].length; + + // 时间复杂度 O(m * n), 空间复杂度 O(1) + let firstRow = false; // 第一行是否应该全部为0 + let firstCol = false; // 第一列是否应该全部为0 + + for (let i = 0; i < m; i++) { + for (let j = 0; j < n; j++) { + const item = matrix[i][j]; + if (item === 0) { + if (i === 0) { + firstRow = true; + } + if (j === 0) { + firstCol = true; + } + matrix[0][j] = 0; + matrix[i][0] = 0; + } + } + } + + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) { + const item = matrix[i][j]; + if (matrix[0][j] == 0 || matrix[i][0] == 0) { + matrix[i][j] = 0; + } + } + } + + // 最后处理第一行和第一列 + + if (firstRow) { + for (let i = 0; i < n; i++) { + matrix[0][i] = 0; + } + } + + if (firstCol) { + for (let i = 0; i < m; i++) { + matrix[i][0] = 0; + } + } + + return matrix; +}; +``` +Python3 Code: + +直接修改第一行和第一列为0的解法: +```python +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + def setRowZeros(matrix: List[List[int]], i:int) -> None: + C = len(matrix[0]) + matrix[i] = [0] * C + + def setColZeros(matrix: List[List[int]], j:int) -> None: + R = len(matrix) + for i in range(R): + matrix[i][j] = 0 + + isCol = False + R = len(matrix) + C = len(matrix[0]) + + for i in range(R): + if matrix[i][0] == 0: + isCol = True + for j in range(1, C): + if matrix[i][j] == 0: + matrix[i][0] = 0 + matrix[0][j] = 0 + for j in range(1, C): + if matrix[0][j] == 0: + setColZeros(matrix, j) + + for i in range(R): + if matrix[i][0] == 0: + setRowZeros(matrix, i) + + if isCol: + setColZeros(matrix, 0) + +``` + +另一种方法是用一个特殊符合标记需要改变的结果,只要这个特殊标记不在我们的题目数据范围(0和1)即可,这里用None。 +```python +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + """ + 这题要解决的问题是,必须有个地方记录判断结果,但又不能影响下一步的判断条件; + 直接改为0的话,会影响下一步的判断条件; + 因此,有一种思路是先改为None,最后再将None改为0; + 从条件上看,如果可以将第一行、第二行作为记录空间,那么,用None应该也不算违背题目条件; + """ + rows = len(matrix) + cols = len(matrix[0]) + # 遍历矩阵,用None记录要改的地方,注意如果是0则要保留,否则会影响下一步判断 + for r in range(rows): + for c in range(cols): + if matrix[r][c] is not None and matrix[r][c] == 0: + # 改值 + for i in range(rows): + matrix[i][c] = None if matrix[i][c] != 0 else 0 + for j in range(cols): + matrix[r][j] = None if matrix[r][j] != 0 else 0 + # 再次遍历,将None改为0 + for r in range(rows): + for c in range(cols): + if matrix[r][c] is None: + matrix[r][c] = 0 +``` + +## 扩展 + +为什么选择第一行第一列,选择其他行和列可以么?为什么? diff --git a/problems/75.sort-colors.md b/problems/75.sort-colors.md new file mode 100644 index 0000000..1282b9e --- /dev/null +++ b/problems/75.sort-colors.md @@ -0,0 +1,73 @@ +## 题目地址 +https://leetcode.com/problems/sort-colors/description/ + +## 题目描述 +Given an array with n objects colored red, white or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white and blue. + +Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively. + +Note: You are not suppose to use the library's sort function for this problem. + +Example: + +Input: [2,0,2,1,1,0] +Output: [0,0,1,1,2,2] +Follow up: + +A rather straight forward solution is a two-pass algorithm using counting sort. +First, iterate the array counting number of 0's, 1's, and 2's, then overwrite array with total number of 0's, then 1's and followed by 2's. +Could you come up with a one-pass algorithm using only constant space? + +## 思路 +这个问题是典型的荷兰国旗问题 (https://en.wikipedia.org/wiki/Dutch_national_flag_problem)。 因为我们可以将红白蓝三色小球想象成条状物,有序排列后正好组成荷兰国旗。 + +有两种解决思路。 + +## 解法一 +- 遍历数组,统计红白蓝三色球(0,1,2)的个数 +- 根据红白蓝三色球(0,1,2)的个数重排数组 + +这种思路的时间复杂度:$O(n)$,需要遍历数组两次。 + +## 解法二 + +我们可以把数组分成三部分,前部(全部是0),中部(全部是1)和后部(全部是2)三个部分。每一个元素(红白蓝分别对应0、1、2)必属于其中之一。将前部和后部各排在数组的前边和后边,中部自然就排好了。 + +我们用三个指针,设置两个指针begin指向前部的末尾的下一个元素(刚开始默认前部无0,所以指向第一个位置),end指向后部开头的前一个位置(刚开始默认后部无2,所以指向最后一个位置),然后设置一个遍历指针current,从头开始进行遍历。 + +这种思路的时间复杂度也是$O(n)$, 只需要遍历数组一次。 + +### 关键点解析 + + +- 荷兰国旗问题 +- counting sort + +### 代码 + +代码支持: Python3 + +Python3 Code: + +``` python +class Solution: + def sortColors(self, nums: List[int]) -> None: + """ + Do not return anything, modify nums in-place instead. + """ + p0 = cur = 0 + p2 = len(nums) - 1 + + while cur <= p2: + if nums[cur] == 0: + nums[cur], nums[p0] = nums[p0], nums[cur] + p0 += 1 + cur += 1 + elif nums[cur] == 2: + nums[cur], nums[p2] = nums[p2], nums[cur] + p2 -= 1 + else: + cur += 1 +``` + + diff --git a/problems/78.subsets-en.md b/problems/78.subsets-en.md new file mode 100644 index 0000000..e8242d9 --- /dev/null +++ b/problems/78.subsets-en.md @@ -0,0 +1,136 @@ +## Problem Link +https://leetcode.com/problems/subsets/description/ + +## Description +``` +Given a set of distinct integers, nums, return all possible subsets (the power set). + +Note: The solution set must not contain duplicate subsets. + +Example: + +Input: nums = [1,2,3] +Output: +[ + [3], + [1], + [2], + [1,2,3], + [1,3], + [2,3], + [1,2], + [] +] + + +``` + +## Solution + +Since this problem is seeking `Subset` not `Extreme Value`, dynamic programming is not an ideal solution. Other approaches should be taken into our consideration. + +Actually, there is a general approach to solve problems similar to this one -- backtracking. Given a [Code Template](https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)) here, it demonstrates how backtracking works with varieties of problems. Apart from current one, many problems can be solved by such a general approach. For more details, please check the `Related Problems` section below. + +Given a picture as followed, let's start with problem-solving ideas of this general solution. + +![backtrack](../assets/problems/backtrack.png) + +See Code Template details below. + +## Key Points + +- Backtrack Approach +- Backtrack Code Template/ Formula + + +## Code + +* Supported Language:JS,C++ + +JavaScript Code: +```js + +/* + * @lc app=leetcode id=78 lang=javascript + * + * [78] Subsets + * + * https://leetcode.com/problems/subsets/description/ + * + * algorithms + * Medium (51.19%) + * Total Accepted: 351.6K + * Total Submissions: 674.8K + * Testcase Example: '[1,2,3]' + * + * Given a set of distinct integers, nums, return all possible subsets (the + * power set). + * + * Note: The solution set must not contain duplicate subsets. + * + * Example: + * + * + * Input: nums = [1,2,3] + * Output: + * [ + * ⁠ [3], + * [1], + * [2], + * [1,2,3], + * [1,3], + * [2,3], + * [1,2], + * [] + * ] + * + */ +function backtrack(list, tempList, nums, start) { + list.push([...tempList]); + for(let i = start; i < nums.length; i++) { + tempList.push(nums[i]); + backtrack(list, tempList, nums, i + 1); + tempList.pop(); + } +} +/** + * @param {number[]} nums + * @return {number[][]} + */ +var subsets = function(nums) { + const list = []; + backtrack(list, [], nums, 0); + return list; +}; +``` +C++ Code: +```C++ +class Solution { +public: + vector> subsets(vector& nums) { + auto ret = vector>(); + auto tmp = vector(); + backtrack(ret, tmp, nums, 0); + return ret; + } + + void backtrack(vector>& list, vector& tempList, vector& nums, int start) { + list.push_back(tempList); + for (auto i = start; i < nums.size(); ++i) { + tempList.push_back(nums[i]); + backtrack(list, tempList, nums, i + 1); + tempList.pop_back(); + } + } +}; +``` + +## Related Problems + +- [39.combination-sum](./39.combination-sum.md)(chinese) +- [40.combination-sum-ii](./40.combination-sum-ii.md)(chinese) +- [46.permutations](./46.permutations.md)(chinese) +- [47.permutations-ii](./47.permutations-ii.md)(chinese) +- [90.subsets-ii](./90.subsets-ii-en.md) +- [113.path-sum-ii](./113.path-sum-ii.md)(chinese) +- [131.palindrome-partitioning](./131.palindrome-partitioning.md)(chinese) diff --git a/problems/78.subsets.md b/problems/78.subsets.md new file mode 100644 index 0000000..00a8864 --- /dev/null +++ b/problems/78.subsets.md @@ -0,0 +1,142 @@ + +## 题目地址 +https://leetcode.com/problems/subsets/description/ + +## 题目描述 +``` +Given a set of distinct integers, nums, return all possible subsets (the power set). + +Note: The solution set must not contain duplicate subsets. + +Example: + +Input: nums = [1,2,3] +Output: +[ + [3], + [1], + [2], + [1,2,3], + [1,3], + [2,3], + [1,2], + [] +] + + +``` + +## 思路 + +这道题目是求集合,并不是`求极值`,因此动态规划不是特别切合,因此我们需要考虑别的方法。 + +这种题目其实有一个通用的解法,就是回溯法。 +网上也有大神给出了这种回溯法解题的 +[通用写法](https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)),这里的所有的解法使用通用方法解答。 +除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。 + +我们先来看下通用解法的解题思路,我画了一张图: + +![backtrack](../assets/problems/backtrack.png) + +通用写法的具体代码见下方代码区。 + +## 关键点解析 + +- 回溯法 +- backtrack 解题公式 + + +## 代码 + +* 语言支持:JS,C++ + +JavaScript Code: +```js + +/* + * @lc app=leetcode id=78 lang=javascript + * + * [78] Subsets + * + * https://leetcode.com/problems/subsets/description/ + * + * algorithms + * Medium (51.19%) + * Total Accepted: 351.6K + * Total Submissions: 674.8K + * Testcase Example: '[1,2,3]' + * + * Given a set of distinct integers, nums, return all possible subsets (the + * power set). + * + * Note: The solution set must not contain duplicate subsets. + * + * Example: + * + * + * Input: nums = [1,2,3] + * Output: + * [ + * ⁠ [3], + * [1], + * [2], + * [1,2,3], + * [1,3], + * [2,3], + * [1,2], + * [] + * ] + * + */ +function backtrack(list, tempList, nums, start) { + list.push([...tempList]); + for(let i = start; i < nums.length; i++) { + tempList.push(nums[i]); + backtrack(list, tempList, nums, i + 1); + tempList.pop(); + } +} +/** + * @param {number[]} nums + * @return {number[][]} + */ +var subsets = function(nums) { + const list = []; + backtrack(list, [], nums, 0); + return list; +}; +``` +C++ Code: +```C++ +class Solution { +public: + vector> subsets(vector& nums) { + auto ret = vector>(); + auto tmp = vector(); + backtrack(ret, tmp, nums, 0); + return ret; + } + + void backtrack(vector>& list, vector& tempList, vector& nums, int start) { + list.push_back(tempList); + for (auto i = start; i < nums.size(); ++i) { + tempList.push_back(nums[i]); + backtrack(list, tempList, nums, i + 1); + tempList.pop_back(); + } + } +}; +``` + +## 相关题目 + +- [39.combination-sum](./39.combination-sum.md) +- [40.combination-sum-ii](./40.combination-sum-ii.md) +- [46.permutations](./46.permutations.md) +- [47.permutations-ii](./47.permutations-ii.md) +- [90.subsets-ii](./90.subsets-ii.md) +- [113.path-sum-ii](./113.path-sum-ii.md) +- [131.palindrome-partitioning](./131.palindrome-partitioning.md) + + diff --git a/problems/79.word-search-en.md b/problems/79.word-search-en.md new file mode 100644 index 0000000..ed3e745 --- /dev/null +++ b/problems/79.word-search-en.md @@ -0,0 +1,242 @@ +## Problem +https://leetcode.com/problems/word-search/ + +## Problem Description +``` +Given a 2D board and a word, find if the word exists in the grid. + +The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once. + +Example: + +board = +[ + ['A','B','C','E'], + ['S','F','C','S'], + ['A','D','E','E'] +] + +Given word = "ABCCED", return true. +Given word = "SEE", return true. +Given word = "ABCB", return false. +``` + +## Solution + +This problem does not give start position, or direction restriction, so +1. Scan board, find starting position with matching word first letter +2. From starting position, DFS (4 (up, down, left, right 4 directions) match word's rest letters +3. For each visited letter, mark it as visited, here use `board[i][j] = '*'` to represent visited. +4. If one direction cannot continue, backtracking, mark start position unvisited, mark `board[i][j] = word[start]` +5. If found any matching, terminate +6. Otherwise, no matching found, return false. + +For example: + +board, word:`SEE` as below pic: +``` +1. Scan board, found board[1,0] = word[0],match word first letter。 +2. DFS(up, down, left, right 4 directions) + +as below pic: +``` +![word search 1](../assets/problems/79.word-search-en-1.png) + +Staring position(1,0), check whether adjacent cells match word next letter `E`. +``` +1. mark current position(1,0)as visited,board[1][0] = '*' +2. Up(0,0)letter='A' not match, +3. Down(2,0)letter='A',not match, +4. Left(-1,0)out of board boundry,not match, +5. right(1,1)letter='F',not match + +as below pic: +``` +![word search 2](../assets/problems/79.word-search-2.png) + +Didn't find matching from starting position, so +``` +1. backtracking,mart start position(1,0)as unvisited, board[1][0] = 'S'. +2. scan board, find next start position(1,3)which match word first letter + +as below pic: +``` +![word search 3](../assets/problems/79.word-search-3.png) + +New starting position(1,3),check whether adjacent cells match word next letter `E`. +``` +1. mark current position(1, 3)as already visited,board[1][3] = '*' +2. Up(0,3)letter='E', match, continue DFS search,refer position(0,3)DFS search steps. +3. Down(2,3)letter='E',match, since #2 DFS didn't find word matching, continue DFS search, rfer to position (2, 3) DFS search steps. +4. Left(1,2)letter='C',not match, +5. Right(1,4)out of board boundry,not match + +as below pic: +``` +![word search 4](../assets/problems/79.word-search-4.png) + +Start position(0,3), DFS,check whether adjacent cells match word next letter `E` +``` +1. marck current position(0,3)already visited,board[0][3] = '*' +2. Up (-1,3)out of board boundry,not match +3. Down(1,3)already visited, +4. Left(0,2)letter='C',not match +5. Right(1,4)out of board boundry,not match + +as below pic: +``` +![word search 5](../assets/problems/79.word-search-5.png) + +Start from position(0,3)not matching word, start position (2, 3) DFS search: +``` +1. Backtracking,mark(0,3)as unvisited。board[0][3] = 'E'. +2. Backtracking to next position(2,3),DFS,check whether adjacent cells match word next letter 'E' +3. Up (1,3)visited, continue +4. Down(3,3)out of board boundry,not match +5. Left(2,2)letter='E', match +6. Right(2,4)out of board boundry,not match + +as below pic: +``` +![word search 6](../assets/problems/79.word-search-6.png) + +Found match with word, return `True`. +![word search 7](../assets/problems/79.word-search-7.png) + +#### Complexity Analysis +- *Time Complexity:* `O(m*n) - m is number of board rows, n is number of board columns ` +- *Space Complexity:* `O(1) - no extra space` + +>**Note**:if use Set or boolean[][] mark position visited,need extra space `O(m*n)`. + +## Key Points + +- Scan board, find start position which match word first letter, DFS +- Remember visited letter +- Backtracking if not found matching + +## Code (`Java/Javascript/Python3`) +*Java Code* +```java +public class LC79WordSearch { + public boolean exist(char[][] board, String word) { + if (board == null || board.length == 0 || board[0].length == 0 + || word == null || word.length() == 0) return true; + int rows = board.length; + int cols = board[0].length; + for (int r = 0; r < rows; r++) { + for (int c = 0; c < cols; c++) { + // scan board, start with word first character + if (board[r][c] == word.charAt(0)) { + if (helper(board, word, r, c, 0)) { + return true; + } + } + } + } + return false; + } + + private boolean helper(char[][] board, String word, int r, int c, int start) { + // already match word all characters, return true + if (start == word.length()) return true; + if (!isValid(board, r, c) || + board[r][c] != word.charAt(start)) return false; + // mark visited + board[r][c] = '*'; + boolean res = helper(board, word, r + 1, c, start + 1) + || helper(board, word, r, c + 1, start + 1) + || helper(board, word, r - 1, c, start + 1) + || helper(board, word, r, c - 1, start + 1); + // backtracking to start position + board[r][c] = word.charAt(start); + return res; + } + + private boolean isValid(char[][] board, int r, int c) { + return r >= 0 && r < board.length && c >= 0 && c < board[0].length; + } +} +``` + +*Python3 Code* +```python +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + m = len(board) + n = len(board[0]) + + def dfs(board, r, c, word, index): + if index == len(word): + return True + if r < 0 or r >= m or c < 0 or c >= n or board[r][c] != word[index]: + return False + board[r][c] = '*' + res = dfs(board, r - 1, c, word, index + 1) or dfs(board, r + 1, c, word, index + 1) or dfs(board, r, c - 1, word, index + 1) or dfs(board, r, c + 1, word, index + 1) + board[r][c] = word[index] + return res + + for r in range(m): + for c in range(n): + if board[r][c] == word[0]: + if dfs(board, r, c, word, 0): + return True +``` + +*Javascript Code* from [**@lucifer**](https://github.com/azl397985856) +```javascript +/* + * @lc app=leetcode id=79 lang=javascript + * + * [79] Word Search + */ +function DFS(board, row, col, rows, cols, word, cur) { + // 边界检查 + if (row >= rows || row < 0) return false; + if (col >= cols || col < 0) return false; + + const item = board[row][col]; + + if (item !== word[cur]) return false; + + if (cur + 1 === word.length) return true; + + // If use HashMap keep track visited letters, then need manual clear HashMap for each backtrack which needs extra space. + // here we use a little trick + board[row][col] = null; + + // UP, DOWN, LEFT, RIGHT + const res = + DFS(board, row + 1, col, rows, cols, word, cur + 1) || + DFS(board, row - 1, col, rows, cols, word, cur + 1) || + DFS(board, row, col - 1, rows, cols, word, cur + 1) || + DFS(board, row, col + 1, rows, cols, word, cur + 1); + + board[row][col] = item; + + return res; +} +/** + * @param {character[][]} board + * @param {string} word + * @return {boolean} + */ +var exist = function(board, word) { + if (word.length === 0) return true; + if (board.length === 0) return false; + + const rows = board.length; + const cols = board[0].length; + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + const hit = DFS(board, i, j, rows, cols, word, 0); + if (hit) return true; + } + } + return false; +}; +``` + +## References +1. [Backtracking Wiki](https://www.wikiwand.com/en/Backtracking) \ No newline at end of file diff --git a/problems/79.word-search.md b/problems/79.word-search.md new file mode 100644 index 0000000..99adc5d --- /dev/null +++ b/problems/79.word-search.md @@ -0,0 +1,237 @@ +## 题目地址 +https://leetcode.com/problems/word-search/ + +## 题目描述 +``` +Given a 2D board and a word, find if the word exists in the grid. + +The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once. + +Example: + +board = +[ + ['A','B','C','E'], + ['S','F','C','S'], + ['A','D','E','E'] +] + +Given word = "ABCCED", return true. +Given word = "SEE", return true. +Given word = "ABCB", return false. +``` + +## 思路 + +在2D表中搜索是否有满足给定单词的字符组合,要求所有字符都是相邻的(方向不限). 题中也没有要求字符的起始和结束位置。 + +在起始位置不确定的情况下,扫描二维数组,找到字符跟给定单词的第一个字符相同的,四个方向(上,下,左,右)分别DFS搜索, +如果任意方向满足条件,则返回结果。不满足,回溯,重新搜索。 + +举例说明:如图二维数组,单词:"SEE" +``` +1. 扫描二维数组,找到board[1,0] = word[0],匹配单词首字母。 +2. 做DFS(上,下,左,右 四个方向) + +如下图: +``` +![word search 1](../assets/problems/79.word-search-1.png) + +起始位置(1,0),判断相邻的字符是否匹配单词下一个字符 `E`. +``` +1. 标记当前字符(1,0)为已经访问过,board[1][0] = '*' +2. 上(0,0)字符为 'A' 不匹配, +3. 下(2,0)字符为 'A',不匹配, +4. 左(-1,0)超越边界,不匹配, +5. 右(1,1)字符 'F',不匹配 + +如下图: +``` +![word search 2](../assets/problems/79.word-search-2.png) + +由于从起始位置DFS都不满足条件,所以 +``` +1. 回溯,标记起始位置(1,0)为未访问。board[1][0] = 'S'. +2. 然后继续扫描二维数组,找到下一个起始位置(1,3) + +如下图: +``` +![word search 3](../assets/problems/79.word-search-3.png) + +起始位置(1,3),判断相邻的字符是否匹配单词下一个字符 `E`. +``` +1. 标记当前字符(1, 3)为已经访问过,board[1][3] = '*' +2. 上(0,3)字符为 'E', 匹配, 继续DFS搜索(参考位置为(0,3)位置DFS搜索步骤描述) +3. 下(2,3)字符为 'E',匹配, #2匹配,先进行#2 DFS搜索,由于#2 DFS搜索没有找到与单词匹配,继续DFS搜索(参考位置为(2,3)DFS搜索步骤描述) +4. 左(1,2)字符为 'C',不匹配, +5. 右(1,4)超越边界,不匹配 + +如下图: +``` +![word search 4](../assets/problems/79.word-search-4.png) + +位置(0,3)满足条件,继续DFS,判断相邻的字符是否匹配单词下一个字符 `E` +``` +1. 标记当前字符(0,3)为已经访问过,board[0][3] = '*' +2. 上 (-1,3)超越边界,不匹配 +3. 下(1,3)已经访问过, +4. 左(0,2)字符为 'C',不匹配 +5. 右(1,4)超越边界,不匹配 + +如下图 +``` +![word search 5](../assets/problems/79.word-search-5.png) + +从位置(0,3)DFS不满足条件,继续位置(2,3)DFS搜索 +``` +1. 回溯,标记起始位置(0,3)为未访问。board[0][3] = 'E'. +2. 回到满足条件的位置(2,3),继续DFS搜索,判断相邻的字符是否匹配单词下一个字符 'E' +3. 上 (1,3)已访问过 +4. 下(3,3)超越边界,不匹配 +5. 左(2,2)字符为 'E',匹配 +6. 右(2,4)超越边界,不匹配 + +如下图: +``` +![word search 6](../assets/problems/79.word-search-6.png) + +单词匹配完成,满足条件,返回 `True`. +![word search 7](../assets/problems/79.word-search-7.png) + +#### 复杂度分析 +- *时间复杂度:* `O(m*n) - m 是二维数组行数, n 是二维数组列数` +- *空间复杂度:* `O(1) - 这里在原数组中标记当前访问过,没有用到额外空间` + +>**注意**:如果用 Set 或者是 boolean[][]来标记字符位置是否已经访问过,需要额外的空间 `O(m*n)`. + +## 关键点分析 +- 遍历二维数组的每一个点,找到起始点相同的字符,做DFS +- DFS过程中,要记录已经访问过的节点,防止重复遍历,这里(Java Code中)用 `*` 表示当前已经访问过,也可以用Set或者是boolean[][]数组记录访问过的节点位置。 +- 是否匹配当前单词中的字符,不符合回溯,这里记得把当前 `*` 重新设为当前字符。如果用Set或者是boolean[][]数组,记得把当前位置重设为没有访问过。 + +## 代码 (`Java/Javascript/Python3`) +*Java Code* +```java +public class LC79WordSearch { + public boolean exist(char[][] board, String word) { + if (board == null || board.length == 0 || board[0].length == 0 + || word == null || word.length() == 0) return true; + int rows = board.length; + int cols = board[0].length; + for (int r = 0; r < rows; r++) { + for (int c = 0; c < cols; c++) { + // scan board, start with word first character + if (board[r][c] == word.charAt(0)) { + if (helper(board, word, r, c, 0)) { + return true; + } + } + } + } + return false; + } + + private boolean helper(char[][] board, String word, int r, int c, int start) { + // already match word all characters, return true + if (start == word.length()) return true; + if (!isValid(board, r, c) || + board[r][c] != word.charAt(start)) return false; + // mark visited + board[r][c] = '*'; + boolean res = helper(board, word, r - 1, c, start + 1) // 上 + || helper(board, word, r + 1, c, start + 1) // 下 + || helper(board, word, r, c - 1, start + 1) // 左 + || helper(board, word, r, c + 1, start + 1); // 右 + // backtracking to start position + board[r][c] = word.charAt(start); + return res; + } + + private boolean isValid(char[][] board, int r, int c) { + return r >= 0 && r < board.length && c >= 0 && c < board[0].length; + } +} +``` + +*Python3 Code* +```python +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + m = len(board) + n = len(board[0]) + + def dfs(board, r, c, word, index): + if index == len(word): + return True + if r < 0 or r >= m or c < 0 or c >= n or board[r][c] != word[index]: + return False + board[r][c] = '*' + res = dfs(board, r - 1, c, word, index + 1) or dfs(board, r + 1, c, word, index + 1) or dfs(board, r, c - 1, word, index + 1) or dfs(board, r, c + 1, word, index + 1) + board[r][c] = word[index] + return res + + for r in range(m): + for c in range(n): + if board[r][c] == word[0]: + if dfs(board, r, c, word, 0): + return True +``` + +*Javascript Code* from [**@lucifer**](https://github.com/azl397985856) +```javascript +/* + * @lc app=leetcode id=79 lang=javascript + * + * [79] Word Search + */ +function DFS(board, row, col, rows, cols, word, cur) { + // 边界检查 + if (row >= rows || row < 0) return false; + if (col >= cols || col < 0) return false; + + const item = board[row][col]; + + if (item !== word[cur]) return false; + + if (cur + 1 === word.length) return true; + + // 如果你用hashmap记录访问的字母, 那么你需要每次backtrack的时候手动清除hashmap,并且需要额外的空间 + // 这里我们使用一个little trick + + board[row][col] = null; + + // 上下左右 + const res = + DFS(board, row + 1, col, rows, cols, word, cur + 1) || + DFS(board, row - 1, col, rows, cols, word, cur + 1) || + DFS(board, row, col - 1, rows, cols, word, cur + 1) || + DFS(board, row, col + 1, rows, cols, word, cur + 1); + + board[row][col] = item; + + return res; +} +/** + * @param {character[][]} board + * @param {string} word + * @return {boolean} + */ +var exist = function(board, word) { + if (word.length === 0) return true; + if (board.length === 0) return false; + + const rows = board.length; + const cols = board[0].length; + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + const hit = DFS(board, i, j, rows, cols, word, 0); + if (hit) return true; + } + } + return false; +}; +``` + +## 参考(References) +1. [回溯法 Wiki](https://www.wikiwand.com/zh/%E5%9B%9E%E6%BA%AF%E6%B3%95) \ No newline at end of file diff --git a/problems/80.remove-duplicates-from-sorted-array-ii.md b/problems/80.remove-duplicates-from-sorted-array-ii.md new file mode 100644 index 0000000..1d2efee --- /dev/null +++ b/problems/80.remove-duplicates-from-sorted-array-ii.md @@ -0,0 +1,106 @@ +## 题目地址(删除排序数组中的重复项 II) + +https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array-ii/description/ + +## 题目描述 + +``` +给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。 + +不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 + +示例 1: + +给定 nums = [1,1,1,2,2,3], + +函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 + +你不需要考虑数组中超出新长度后面的元素。 +示例 2: + +给定 nums = [0,0,1,1,1,1,2,3,3], + +函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。 + +你不需要考虑数组中超出新长度后面的元素。 +说明: + +为什么返回数值是整数,但输出的答案是数组呢? + +请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 + +你可以想象内部操作如下: + +// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 +int len = removeDuplicates(nums); + +// 在函数里修改输入数组对于调用者是可见的。 +// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 +for (int i = 0; i < len; i++) { + print(nums[i]); +} + +``` + +## 思路 + +”删除排序“类题目截止到现在(2020-1-15)一共有四道题: + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gax0eadc5ej30x60ce76i.jpg) + +这道题是[26.remove-duplicates-from-sorted-array](./26.remove-duplicates-from-sorted-array.md) 的进阶版本,唯一的不同是不再是全部元素唯一,而是全部元素不超过 2 次。实际上这种问题可以更抽象一步,即“删除排序数组中的重复项,使得相同数字最多出现 k 次” +。 那么这道题 k 就是 2, 26.remove-duplicates-from-sorted-array 的 k 就是 1。 + +上一题我们使用了快慢指针来实现,这道题也是一样,只不过逻辑稍有不同。 其实快慢指针本质是读写指针,在这里我们的快指针实际上就是读指针,而慢指针恰好相当于写指针。”快慢指针的说法“便于描述和记忆,“读写指针”的说法更便于理解本质。本文中,以下内容均描述为快慢指针。 + +- 初始化快慢指针 slow , fast ,全部指向索引为 0 的元素。 +- fast 每次移动一格 +- 慢指针选择性移动,即只有写入数据之后才移动。是否写入数据取决于 slow - 2 对应的数字和 fast 对应的数字是否一致。 +- 如果一致,我们不应该写。 否则我们就得到了三个相同的数字,不符合题意 +- 如果不一致,我们需要将 fast 指针的数据写入到 slow 指针。 +- 重复这个过程,直到 fast 走到头,说明我们已无数字可写。 + +图解(红色的两个数字,表示我们需要比较的两个数字): + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gax0oyt4yhj30n10hpdgc.jpg) + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gax0p3ri3ij30ga0880ss.jpg) + +## 关键点分析 + +- 快慢指针 +- 读写指针 +- 删除排序问题 + +## 代码 + +代码支持: Python + +Python Code: + +```python +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: + # 写指针 + i = 0 + K = 2 + for num in nums: + if i < K or num != nums[i-K]: + nums[i] = num + i += 1 + return i +``` + +基于这套代码,你可以轻易地实现 k 为任意正整数的算法。 + +## 相关题目 + +正如上面所说,相关题目一共有三道(排除自己)。其中一道我们仓库已经讲到了。剩下两道原理类似,但是实际代码和细节有很大不同,原因就在于数组可以随机访问,而链表不行。 感兴趣的可以做一下剩下的两道链表题。 + +- 82. 删除排序链表中的重复元素 II + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gax0txa7gbj31lq0tg0zm.jpg) + +- 83. 删除排序链表中的重复元素 + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gax0uzm0euj318c0se44t.jpg) diff --git a/problems/820.short-encoding-of-words.md b/problems/820.short-encoding-of-words.md new file mode 100644 index 0000000..037765e --- /dev/null +++ b/problems/820.short-encoding-of-words.md @@ -0,0 +1,121 @@ +## 题目地址(820. 单词的压缩编码) + +https://leetcode-cn.com/problems/walking-robot-simulation/submissions/ + +## 题目描述 + +``` +给定一个单词列表,我们将这个列表编码成一个索引字符串 S 与一个索引列表 A。 + +例如,如果这个列表是 ["time", "me", "bell"],我们就可以将其表示为 S = "time#bell#" 和 indexes = [0, 2, 5]。 + +对于每一个索引,我们可以通过从字符串 S 中索引的位置开始读取字符串,直到 "#" 结束,来恢复我们之前的单词列表。 + +那么成功对给定单词列表进行编码的最小字符串长度是多少呢? + +  + +示例: + +输入: words = ["time", "me", "bell"] +输出: 10 +说明: S = "time#bell#" , indexes = [0, 2, 5] 。 +  + +提示: + +1 <= words.length <= 2000 +1 <= words[i].length <= 7 +每个单词都是小写字母 。 + +``` + + +## 思路 + +读完题目之后就发现这题是一个后缀树。 因此符合直觉的想法是使用前缀树 + 倒序插入的形式来模拟后缀树。 + + +下面的代码看起来复杂,但是很多题目我都是用这个模板,稍微调整下细节就能AC。我这里总结了一套[前缀树专题](https://github.com/azl397985856/leetcode/blob/master/thinkings/trie.md) + +![image.png](https://pic.leetcode-cn.com/e54b2b2d133dc0071e552138eb1f64617bdc1ecba415b6ba65b022177f343a28-image.png) + +前缀树的 api 主要有以下几个: + +- `insert(word)`: 插入一个单词 +- `search(word)`:查找一个单词是否存在 +- `startWith(word)`: 查找是否存在以 word 为前缀的单词 + +其中 startWith 是前缀树最核心的用法,其名称前缀树就从这里而来。大家可以先拿 208 题开始,熟悉一下前缀树,然后再尝试别的题目。 + +一个前缀树大概是这个样子: + +![image.png](https://pic.leetcode-cn.com/5707f704af10748fe17f65d8201e6e5d93f5595d5907bfecb242ad2a1a149994-image.png) + + +如图每一个节点存储一个字符,然后外加一个控制信息表示是否是单词结尾,实际使用过程可能会有细微差别,不过变化不大。 + + +这道题需要考虑edge case, 比如这个列表是 ["time", "time", "me", "bell"] 这种包含重复元素的情况,这里我使用hashset来去重。 + +## 关键点 + +- 前缀树 +- 去重 + +## 代码 + +```python +class Trie: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.Trie = {} + + def insert(self, word): + """ + Inserts a word into the trie. + :type word: str + :rtype: void + """ + curr = self.Trie + for w in word: + if w not in curr: + curr[w] = {} + curr = curr[w] + curr['#'] = 1 + + def isTail(self, word): + """ + Returns if the word is in the trie. + :type word: str + :rtype: bool + """ + curr = self.Trie + for w in word: + curr = curr[w] + return len(curr) == 1 +class Solution: + def minimumLengthEncoding(self, words: List[str]) -> int: + trie = Trie() + cnt = 0 + words = set(words) + for word in words: + trie.insert(word[::-1]) + for word in words: + if trie.isTail(word[::-1]): + cnt += len(word) + 1 + return cnt + +``` + +***复杂度分析*** +- 时间复杂度:$O(N)$,其中N为单词长度列表中的总字符数,比如["time", "me"],就是 4 + 2 = 6。 +- 空间复杂度:$O(N)$,其中N为单词长度列表中的总字符数,比如["time", "me"],就是 4 + 2 = 6。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) + diff --git a/problems/84.largest-rectangle-in-histogram.md b/problems/84.largest-rectangle-in-histogram.md new file mode 100644 index 0000000..8b618f8 --- /dev/null +++ b/problems/84.largest-rectangle-in-histogram.md @@ -0,0 +1,155 @@ +## 题目地址(84. 柱状图中最大的矩形) + +https://leetcode-cn.com/problems/largest-rectangle-in-histogram/ + +## 题目描述 + +` +给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 + +求在该柱状图中,能够勾勒出来的矩形的最大面积。 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gch1kvdoy5j305805oaa1.jpg) + +以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为  [2,1,5,6,2,3]。 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gch1l4m3clj305805owem.jpg) + +图中阴影部分为所能勾勒出的最大矩形面积,其面积为  10  个单位。 + +示例: + +输入:[2,1,5,6,2,3] +输出:10 + +## 暴力枚举 - 左右端点法(TLE) + +### 思路 + +我们暴力尝试`所有可能的矩形`。由于矩阵是二维图形, 我我们可以使用`左右两个端点来唯一确认一个矩阵`。因此我们使用双层循环枚举所有的可能性即可。 而矩形的面积等于`(右端点坐标 - 左端点坐标 + 1) * 最小的高度`,最小的高度我们可以在遍历的时候顺便求出。 + +### 代码 + +```python +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + n, ans = len(heights), 0 + if n != 0: + ans = heights[0] + for i in range(n): + height = heights[i] + for j in range(i, n): + height = min(height, heights[j]) + ans = max(ans, (j - i + 1) * height) + return ans +``` + +**复杂度分析** + +- 时间复杂度:$O(N^2)$ +- 空间复杂度:$O(1)$ + +## 暴力枚举 - 中心扩展法(TLE) + +### 思路 + +我们仍然暴力尝试`所有可能的矩形`。只不过我们这一次从中心向两边进行扩展。对于每一个 i,我们计算出其左边第一个高度小于它的索引 p,同样地,计算出右边第一个高度小于它的索引 q。那么以 i 为最低点能够构成的面积就是`(q - p - 1) * heights[i]`。 这种算法毫无疑问也是正确的。 我们证明一下,假设 f(i) 表示求以 i 为最低点的情况下,所能形成的最大矩阵面积。那么原问题转化为`max(f(0), f(1), f(2), ..., f(n - 1))`。 + +具体算法如下: + +- 我们使用 l 和 r 数组。l[i] 表示 左边第一个高度小于它的索引,r[i] 表示 右边第一个高度小于它的索引。 +- 我们从前往后求出 l,再从后往前计算出 r。 +- 再次遍历求出所有的可能面积,并取出最大的。 + +### 代码 + +```python +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + n = len(heights) + l, r, ans = [-1] * n, [n] * n, 0 + for i in range(1, n): + j = i - 1 + while j >= 0 and heights[j] >= heights[i]: + j -= 1 + l[i] = j + for i in range(n - 2, -1, -1): + j = i + 1 + while j < n and heights[j] >= heights[i]: + j += 1 + r[i] = j + for i in range(n): + ans = max(ans, heights[i] * (r[i] - l[i] - 1)) + return ans + +``` + +**复杂度分析** + +- 时间复杂度:$O(N^2)$ +- 空间复杂度:$O(N)$ + +## 优化中心扩展法(Accepted) + +### 思路 + +实际上我们内层循环没必要一步一步移动,我们可以直接将`j -= 1` 改成 `j = l[j]`, `j += 1` 改成 `j = r[j]`。 + +### 代码 + +```python +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + n = len(heights) + l, r, ans = [-1] * n, [n] * n, 0 + + for i in range(1, n): + j = i - 1 + while j >= 0 and heights[j] >= heights[i]: + j = l[j] + l[i] = j + for i in range(n - 2, -1, -1): + j = i + 1 + while j < n and heights[j] >= heights[i]: + j = r[j] + r[i] = j + for i in range(n): + ans = max(ans, heights[i] * (r[i] - l[i] - 1)) + return ans + +``` + +**复杂度分析** + +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(N)$ + +## 单调栈(Accepted) + +### 思路 + +实际上,读完第二种方法的时候,你应该注意到了。我们的核心是求左边第一个比 i 小的和右边第一个比 i 小的。 如果你熟悉单调栈的话,那么应该会想到这是非常适合使用单调栈来处理的场景。 + +为了简单起见,我在 heights 首尾添加了两个哨兵元素,这样可以减少边界处理的额外代码。 + +### 代码 + +```python +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 + for i in range(n + 2): + while st and heights[st[-1]] > heights[i]: + ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) + st.append(i) + return ans +``` + +**复杂度分析** + +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(N)$ + +欢迎关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) diff --git a/problems/85.maximal-rectangle.md b/problems/85.maximal-rectangle.md new file mode 100644 index 0000000..78e2593 --- /dev/null +++ b/problems/85.maximal-rectangle.md @@ -0,0 +1,78 @@ +## 题目地址(85. 最大矩形) + +https://leetcode-cn.com/problems/maximal-rectangle/ + +## 题目描述 + +给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 + +示例: + +输入: +``` +[ + ["1","0","1","0","0"], + ["1","0","1","1","1"], + ["1","1","1","1","1"], + ["1","0","0","1","0"] +] +``` +输出:6 + +## 思路 + +我在 [【84. 柱状图中最大的矩形】多种方法(Python3)](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/84-zhu-zhuang-tu-zhong-zui-da-de-ju-xing-duo-chong/ "【84. 柱状图中最大的矩形】多种方法(Python3)") 使用了多种方法来解决。 然而在这道题,我们仍然可以使用完全一样的思路去完成。 不熟悉的可以看下我的题解。本题解是基于那道题的题解来进行的。 + +拿题目给的例子来说: + +``` +[ + ["1","0","1","0","0"], + ["1","0","1","1","1"], + ["1","1","1","1","1"], + ["1","0","0","1","0"] +] +``` + +我们逐行扫描得到 `84. 柱状图中最大的矩形` 中的 heights 数组: + +![](https://pic.leetcode-cn.com/aaa258e37c34d5028f56b1c172300c278ff439f209431010561d7b8a7d8eae2a.jpg) + +这样我们就可以使用`84. 柱状图中最大的矩形` 中的解法来进行了,这里我们使用单调栈来解。 + +## 代码 + +```python +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + n, heights, st, ans = len(heights), [0] + heights + [0], [], 0 + for i in range(n + 2): + while st and heights[st[-1]] > heights[i]: + ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1)) + st.append(i) + + return ans + def maximalRectangle(self, matrix: List[List[str]]) -> int: + m = len(matrix) + if m == 0: return 0 + n = len(matrix[0]) + heights = [0] * n + ans = 0 + for i in range(m): + for j in range(n): + if matrix[i][j] == "0": + heights[j] = 0 + else: + heights[j] += 1 + ans = max(ans, self.largestRectangleArea(heights)) + return ans + +``` + +**复杂度分析** +- 时间复杂度:$O(M * N)$ +- 空间复杂度:$O(N)$ + +欢迎关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) \ No newline at end of file diff --git a/problems/86.partition-list.md b/problems/86.partition-list.md new file mode 100644 index 0000000..12b07ed --- /dev/null +++ b/problems/86.partition-list.md @@ -0,0 +1,144 @@ +## 题目地址 +https://leetcode.com/problems/partition-list/description/ + +## 题目描述 +Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x. + +You should preserve the original relative order of the nodes in each of the two partitions. + +Example: + +Input: head = 1->4->3->2->5->2, x = 3 +Output: 1->2->2->4->3->5 + +## 思路 + +- 设定两个虚拟节点,dummyHead1用来保存小于该值的链表,dummyHead2来保存大于等于该值的链表 + +- 遍历整个原始链表,将小于该值的放于dummyHead1中,其余的放置在dummyHead2中 + +遍历结束后,将dummyHead2插入到dummyHead1后面 + +![86.partition-list](../assets/86.partition-list.gif) + +(图片来自: https://github.com/MisterBooo/LeetCodeAnimation) +## 关键点解析 + +- 链表的基本操作(遍历) +- 虚拟节点dummy 简化操作 +- 遍历完成之后记得`currentL1.next = null;`否则会内存溢出 + +> 如果单纯的遍历是不需要上面操作的,但是我们的遍历会导致currentL1.next和currentL2.next +中有且仅有一个不是null, 如果不这么操作的话会导致两个链表成环,造成溢出。 + + +## 代码 + +* 语言支持: Javascript,Python3 + +```js +/* + * @lc app=leetcode id=86 lang=javascript + * + * [86] Partition List + * + * https://leetcode.com/problems/partition-list/description/ + * + * algorithms + * Medium (36.41%) + * Total Accepted: 155.1K + * Total Submissions: 425.1K + * Testcase Example: '[1,4,3,2,5,2]\n3' + * + * Given a linked list and a value x, partition it such that all nodes less + * than x come before nodes greater than or equal to x. + * + * You should preserve the original relative order of the nodes in each of the + * two partitions. + * + * Example: + * + * + * Input: head = 1->4->3->2->5->2, x = 3 + * Output: 1->2->2->4->3->5 + * + * + */ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} head + * @param {number} x + * @return {ListNode} + */ +var partition = function(head, x) { + const dummyHead1 = { + next: null + } + const dummyHead2 = { + next: null + } + + let current = { + next: head + }; + let currentL1 = dummyHead1; + let currentL2 = dummyHead2; + while(current.next) { + current = current.next; + if (current.val < x) { + currentL1.next = current; + currentL1 = current; + } else { + currentL2.next = current; + currentL2 = current; + } + } + + currentL2.next = null; + + currentL1.next = dummyHead2.next; + + return dummyHead1.next; +}; +``` +Python3 Code: +```python +class Solution: + def partition(self, head: ListNode, x: int) -> ListNode: + """在原链表操作,思路基本一致,只是通过指针进行区分而已""" + # 在链表最前面设定一个初始node作为锚点,方便返回最后的结果 + first_node = ListNode(0) + first_node.next = head + # 设计三个指针,一个指向小于x的最后一个节点,即前后分离点 + # 一个指向当前遍历节点的前一个节点 + # 一个指向当前遍历的节点 + sep_node = first_node + pre_node = first_node + current_node = head + + while current_node is not None: + if current_node.val < x: + # 注意有可能出现前一个节点就是分离节点的情况 + if pre_node is sep_node: + pre_node = current_node + sep_node = current_node + current_node = current_node.next + else: + # 这段次序比较烧脑 + pre_node.next = current_node.next + current_node.next = sep_node.next + sep_node.next = current_node + sep_node = current_node + current_node = pre_node.next + else: + pre_node = current_node + current_node = pre_node.next + + return first_node.next +``` diff --git a/problems/874.walking-robot-simulation.md b/problems/874.walking-robot-simulation.md new file mode 100644 index 0000000..f2ed8b1 --- /dev/null +++ b/problems/874.walking-robot-simulation.md @@ -0,0 +1,118 @@ +## 题目地址(874. 模拟行走机器人) + +https://leetcode-cn.com/problems/walking-robot-simulation/submissions/ + +## 题目描述 + +``` +机器人在一个无限大小的网格上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令: + +-2:向左转 90 度 +-1:向右转 90 度 +1 <= x <= 9:向前移动 x 个单位长度 +在网格上有一些格子被视为障碍物。 + +第 i 个障碍物位于网格点  (obstacles[i][0], obstacles[i][1]) + +如果机器人试图走到障碍物上方,那么它将停留在障碍物的前一个网格方块上,但仍然可以继续该路线的其余部分。 + +返回从原点到机器人的最大欧式距离的平方。 + +  + +示例 1: + +输入: commands = [4,-1,3], obstacles = [] +输出: 25 +解释: 机器人将会到达 (3, 4) +示例 2: + +输入: commands = [4,-1,4,-2,4], obstacles = [[2,4]] +输出: 65 +解释: 机器人在左转走到 (1, 8) 之前将被困在 (1, 4) 处 +  + +提示: + +0 <= commands.length <= 10000 +0 <= obstacles.length <= 10000 +-30000 <= obstacle[i][0] <= 30000 +-30000 <= obstacle[i][1] <= 30000 +答案保证小于 2 ^ 31 + + +``` + +## 思路 + +这道题之所以是简单难度,是因为其没有什么技巧。你只需要看懂题目描述,然后把题目描述转化为代码即可。 + +唯一需要注意的是查找障碍物的时候如果你采用的是`线形查找`会很慢,很可能会超时。 + +> 我实际测试了一下,确实会超时 + +- 一种方式是使用排序,然后二分查找,如果采用基于比较的排序算法,那么这种算法的瓶颈在于排序本身,也就是$O(NlogN)$。 +- 另一种方式是使用集合,将 obstacles 放入集合,然后需要的时候进行查询,查询的时候的时间复杂度为$O(1)$。 + +这里我们采用第二种方式。 + +接下来我们来“翻译”一下题目。 + +- 由于机器人只能往前走。因此机器人往东西南北哪个方向走取决于它的`朝向`。 +- 我们使用枚举来表示当前机器人的`朝向`。 +- 题目只有两种方式改变`朝向`,一种是左转(-2),另一种是右转(-1)。 +- 题目要求的是机器人在`运动过程中距离原点的最大值`,而不是最终位置距离原点的距离。 + +为了代码书写简单,我建立了一个直角坐标系。用`机器人的朝向和 x 轴正方向的夹角度数`来作为枚举值,并且这个度数是 `0 <= deg < 360`。我们不难知道,其实这个取值就是`0`, `90`,`180`,`270` 四个值。那么当 0 度的时候,我们只需要不断地 x+1,90 度的时候我们不断地 y + 1 等等。 + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbdnsywx97j31020r8gmt.jpg) + +## 关键点解析 + +- 理解题意,这道题容易理解错题意,求解为`最终位置距离原点的距离` +- 建立坐标系 +- 使用集合简化线形查找的时间复杂度。 + +## 代码 + +代码支持: Python3 + +Python3 Code: + +```python +class Solution: + def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int: + pos = [0, 0] + deg = 90 + ans = 0 + obstaclesSet = set(map(tuple, obstacles)) + + for command in commands: + if command == -1: + deg = (deg + 270) % 360 + elif command == -2: + deg = (deg + 90) % 360 + else: + if deg == 0: + i = 0 + while i < command and not (pos[0] + 1, pos[1]) in obstaclesSet: + pos[0] += 1 + i += 1 + if deg == 90: + i = 0 + while i < command and not (pos[0], pos[1] + 1) in obstaclesSet: + pos[1] += 1 + i += 1 + if deg == 180: + i = 0 + while i < command and not (pos[0] - 1, pos[1]) in obstaclesSet: + pos[0] -= 1 + i += 1 + if deg == 270: + i = 0 + while i < command and not (pos[0], pos[1] - 1) in obstaclesSet: + pos[1] -= 1 + i += 1 + ans = max(ans, pos[0] ** 2 + pos[1] ** 2) + return ans +``` diff --git a/problems/875.koko-eating-bananas.md b/problems/875.koko-eating-bananas.md new file mode 100644 index 0000000..197effc --- /dev/null +++ b/problems/875.koko-eating-bananas.md @@ -0,0 +1,155 @@ +## 题目地址 +https://leetcode.com/problems/koko-eating-bananas/description/ + +## 题目描述 +``` +Koko loves to eat bananas. There are N piles of bananas, the i-th pile has piles[i] bananas. The guards have gone and will come back in H hours. + +Koko can decide her bananas-per-hour eating speed of K. Each hour, she chooses some pile of bananas, and eats K bananas from that pile. If the pile has less than K bananas, she eats all of them instead, and won't eat any more bananas during this hour. + +Koko likes to eat slowly, but still wants to finish eating all the bananas before the guards come back. + +Return the minimum integer K such that she can eat all the bananas within H hours. + + + +Example 1: + +Input: piles = [3,6,7,11], H = 8 +Output: 4 +Example 2: + +Input: piles = [30,11,23,4,20], H = 5 +Output: 30 +Example 3: + +Input: piles = [30,11,23,4,20], H = 6 +Output: 23 + + +Note: + +1 <= piles.length <= 10^4 +piles.length <= H <= 10^9 +1 <= piles[i] <= 10^9 + +``` + +## 思路 +符合直觉的做法是,选择最大的堆的香蕉数,然后试一下能不能行,如果不行则直接返回上次计算的结果, +如果行,我们减少1个香蕉,试试行不行,依次类推。计算出刚好不行的即可。这种解法的时间复杂度是O(n)。 + +这道题如果能看出来是二分法解决,那么其实很简单。为什么它是二分问题呢? +我这里画了个图,我相信你看了就明白了。 + +![koko-eating-bananas](../assets/problems/koko-eating-bananas.png) + +## 关键点解析 + +- 二分查找 + + +## 代码 + +```js +/* + * @lc app=leetcode id=875 lang=javascript + * + * [875] Koko Eating Bananas + * + * https://leetcode.com/problems/koko-eating-bananas/description/ + * + * algorithms + * Medium (44.51%) + * Total Accepted: 11.3K + * Total Submissions: 24.8K + * Testcase Example: '[3,6,7,11]\n8' + * + * Koko loves to eat bananas.  There are N piles of bananas, the i-th pile has + * piles[i] bananas.  The guards have gone and will come back in H hours. + * + * Koko can decide her bananas-per-hour eating speed of K.  Each hour, she + * chooses some pile of bananas, and eats K bananas from that pile.  If the + * pile has less than K bananas, she eats all of them instead, and won't eat + * any more bananas during this hour. + * + * Koko likes to eat slowly, but still wants to finish eating all the bananas + * before the guards come back. + * + * Return the minimum integer K such that she can eat all the bananas within H + * hours. + * + * + * + * + * + * + * + * Example 1: + * + * + * Input: piles = [3,6,7,11], H = 8 + * Output: 4 + * + * + * + * Example 2: + * + * + * Input: piles = [30,11,23,4,20], H = 5 + * Output: 30 + * + * + * + * Example 3: + * + * + * Input: piles = [30,11,23,4,20], H = 6 + * Output: 23 + * + * + * + * + * Note: + * + * + * 1 <= piles.length <= 10^4 + * piles.length <= H <= 10^9 + * 1 <= piles[i] <= 10^9 + * + * + * + * + * + */ + + function canEatAllBananas(piles, H, mid) { + let h = 0; + for(let pile of piles) { + h += Math.ceil(pile / mid); + } + + return h <= H; + } +/** + * @param {number[]} piles + * @param {number} H + * @return {number} + */ +var minEatingSpeed = function(piles, H) { + let lo = 1, + hi = Math.max(...piles); + + while(lo <= hi) { + let mid = lo + ((hi - lo) >> 1); + if (canEatAllBananas(piles, H, mid)) { + hi = mid - 1; + } else { + lo = mid + 1; + } + } + + return lo; // 不能选择hi +}; +``` + diff --git a/problems/877.stone-game.md b/problems/877.stone-game.md new file mode 100644 index 0000000..44468a8 --- /dev/null +++ b/problems/877.stone-game.md @@ -0,0 +1,83 @@ +## 题目地址 + +https://leetcode.com/problems/stone-game/description/ + +## 题目描述 + +``` +Alex and Lee play a game with piles of stones. There are an even number of piles arranged in a row, and each pile has a positive integer number of stones piles[i]. + +The objective of the game is to end with the most stones. The total number of stones is odd, so there are no ties. + +Alex and Lee take turns, with Alex starting first. Each turn, a player takes the entire pile of stones from either the beginning or the end of the row. This continues until there are no more piles left, at which point the person with the most stones wins. + +Assuming Alex and Lee play optimally, return True if and only if Alex wins the game. + + + +Example 1: + +Input: [5,3,4,5] +Output: true +Explanation: +Alex starts first, and can only take the first 5 or the last 5. +Say he takes the first 5, so that the row becomes [3, 4, 5]. +If Lee takes 3, then the board is [4, 5], and Alex takes 5 to win with 10 points. +If Lee takes the last 5, then the board is [3, 4], and Alex takes 4 to win with 9 points. +This demonstrated that taking the first 5 was a winning move for Alex, so we return true. + + +Note: + +2 <= piles.length <= 500 +piles.length is even. +1 <= piles[i] <= 500 +sum(piles) is odd. + +``` + +## 思路 + +由于 piles 是偶数的,并且 piles 的总和是奇数的。 + +因此 Alex`可以做到`要不拿的全部是奇数,要么全部是偶数。 + +举个例子: 比如 Alex 第一次先拿第一个 + +这里有两种情况: + +1. Lee 如果拿了第二块(偶数),那么 Alex 继续拿第三块,以此类推。。。 + +2. Lee 如果拿了最后一块(偶数),那么 Alex 继续拿倒数第二块,以此类推。。。 + +因此 Alex`可以`做到只拿奇数或者偶数,只是他可以控制的,因此他要做的就是数一下,奇数加起来多还是偶数加起来多就好了。 +奇数多就全部选奇数,偶数就全部选偶数。 Lee 是没有这种自由权的。 + +## 关键点解析 + +- 可以用 DP(动态规划) + +- 可以从数学的角度去分析 + +> ......(😅) + +## 代码 + +```js +/** + * @param {number[]} piles + * @return {boolean} + */ +var stoneGame = function(piles) { + return true; +}; +``` + +## 扩展 + +腾讯面试题:一共 100 只弓箭 你和你的对手共用。你们每次只能射出一支箭或者两支箭,射击交替进行,设计一个算法,保证自己获胜。 + +答案: 先手,剩下的是 3 的倍数就行(100-1=99),然后按照 3 的倍数射箭必赢。 +比如你先拿了 1,剩下 99 个。 对手拿了 1,你就拿 2。这样持续 33 次就赢了。如果对手拿了 2 个,你就拿 1 个,这样持续 33 次你也是赢的。 + +> 这是一种典型的博弈问题, 你和对手交替进行,对手的行动影响你接下来的策略。 这算是一种最简单的博弈问题了 diff --git a/problems/88.merge-sorted-array.md b/problems/88.merge-sorted-array.md new file mode 100644 index 0000000..9d7be96 --- /dev/null +++ b/problems/88.merge-sorted-array.md @@ -0,0 +1,195 @@ +## 题目地址 + +https://leetcode-cn.com/problems/merge-sorted-array/ + +## 题目描述 + +``` +给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。 + +说明: + +初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 +你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 +示例: + +输入: +nums1 = [1,2,3,0,0,0], m = 3 +nums2 = [2,5,6], n = 3 + +输出: [1,2,2,3,5,6] +``` + +## 思路 + +符合直觉的做法是`将nums2插到num1的末尾, 然后排序` + + +具体代码: + +```js + // 这种解法连m都用不到 + // 这显然不是出题人的意思 + if (n === 0) return; + let current2 = 0; + for(let i = nums1.length - 1; i >= nums1.length - n ; i--) { + nums1[i] = nums2[current2++]; + } + nums1.sort((a, b) => a - b); // 当然你可以自己写排序,这里懒得写了,因为已经偏离了题目本身 + +``` + +这道题目其实和基本排序算法中的`merge sort`非常像,但是 merge sort 很多时候,合并的时候我们通常是 +新建一个数组,这样就很简单。 但是这道题目要求的是`原地修改`. + +这就和 merge sort 的 merge 过程有点不同,我们先来回顾一下 merge sort 的 merge 过程。 + +merge 的过程`可以`是先比较两个数组的头元素,然后将较小的推到最终的数组中,并将其从原数组中出队列。 +循环直到两个数组都为空。 + +具体代码如下: + +```js +// 将nums1 和 nums2 合并 +function merge(nums1, nums2) { + let ret = []; + while (nums1.length || nums2.length) { + // 为了方便大家理解,这里代码有点赘余 + if (nums1.length === 0) { + ret.push(nums2.shift()); + continue; + } + + if (nums2.length === 0) { + ret.push(nums1.shift()); + continue; + } + const a = nums1[0]; + const b = nums2[0]; + if (a > b) { + ret.push(nums2.shift()); + } else { + ret.push(nums1.shift()); + } + } + return ret; +} +``` + +这里要求原地修改,其实我们能只要从后往前比较,并从后往前插入即可。 + +我们需要三个指针: + +1. current 用于记录当前填补到那个位置了 + +2. m 用于记录 nums1 数组处理到哪个元素了 + +3. n 用于记录 nums2 数组处理到哪个元素了 + +如图所示: + +- 灰色代表 num2 数组已经处理过的元素 +- 红色代表当前正在进行比较的元素 +- 绿色代表已经就位的元素 + +![88.merge-sorted-array-1](../assets/problems/88.merge-sorted-array-1.png) +![88.merge-sorted-array-2](../assets/problems/88.merge-sorted-array-2.png) +![88.merge-sorted-array-3](../assets/problems/88.merge-sorted-array-3.png) + +## 关键点解析 + +- 从后往前比较,并从后往前插入 + +## 代码 + +代码支持:Python3, C++, JavaScript + + +JavaSCript Code: + +```js +var merge = function(nums1, m, nums2, n) { + // 设置一个指针,指针初始化指向nums1的末尾(根据#62,应该是index为 m+n-1 的位置,因为nums1的长度有可能更长) + // 然后不断左移指针更新元素 + let current = m + n - 1; + + while (current >= 0) { + // 没必要继续了 + if (n === 0) return; + + // 为了方便大家理解,这里代码有点赘余 + if (m < 1) { + nums1[current--] = nums2[--n]; + continue; + } + + if (n < 1) { + nums1[current--] = nums1[--m]; + continue; + } + // 取大的填充 nums1的末尾 + // 然后更新 m 或者 n + if (nums1[m - 1] > nums2[n - 1]) { + nums1[current--] = nums1[--m]; + } else { + nums1[current--] = nums2[--n]; + } + } +}; +``` + +C++ code: +``` +class Solution { +public: + void merge(vector& nums1, int m, vector& nums2, int n) { + int current = m + n - 1; + while (current >= 0) { + if (n == 0) return; + if (m < 1) { + nums1[current--] = nums2[--n]; + continue; + } + if (n < 1) { + nums1[current--] = nums1[--m]; + continue; + } + if (nums1[m - 1] > nums2[n - 1]) nums1[current--] = nums1[--m]; + else nums1[current--] = nums2[--n]; + } + } +}; +``` + +Python Code +```python +class Solution: + def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: + """ + Do not return anything, modify nums1 in-place instead. + """ + # 整体思路相似,只不过没有使用 current 指针记录当前填补位置 + while m > 0 and n > 0: + if nums1[m-1] <= nums2[n-1]: + nums1[m+n-1] = nums2[n-1] + n -= 1 + else: + nums1[m+n-1] = nums1[m-1] + m -=1 + """ + 由于没有使用 current,第一步比较结束后有两种情况: + 1. 指针 m>0,n=0,此时不需要做任何处理 + 2. 指针 n>0,m=0,此时需要将 nums2 指针左侧元素全部拷贝到 nums1 的前 n 位 + """ + if n > 0: + nums1[:n] = nums2[:n] +``` + + +**复杂度分析** +- 时间复杂度:$O(M + N)$ +- 空间复杂度:$O(1)$ + +欢迎关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) diff --git a/problems/887.super-egg-drop.md b/problems/887.super-egg-drop.md new file mode 100644 index 0000000..2d6d9b4 --- /dev/null +++ b/problems/887.super-egg-drop.md @@ -0,0 +1,130 @@ + +## 题目地址 +https://leetcode.com/problems/super-egg-drop/description/ + +## 题目描述 + +``` +You are given K eggs, and you have access to a building with N floors from 1 to N. + +Each egg is identical in function, and if an egg breaks, you cannot drop it again. + +You know that there exists a floor F with 0 <= F <= N such that any egg dropped at a floor higher than F will break, and any egg dropped at or below floor F will not break. + +Each move, you may take an egg (if you have an unbroken one) and drop it from any floor X (with 1 <= X <= N). + +Your goal is to know with certainty what the value of F is. + +What is the minimum number of moves that you need to know with certainty what F is, regardless of the initial value of F? + + + +Example 1: + +Input: K = 1, N = 2 +Output: 2 +Explanation: +Drop the egg from floor 1. If it breaks, we know with certainty that F = 0. +Otherwise, drop the egg from floor 2. If it breaks, we know with certainty that F = 1. +If it didn't break, then we know with certainty F = 2. +Hence, we needed 2 moves in the worst case to know what F is with certainty. +Example 2: + +Input: K = 2, N = 6 +Output: 3 +Example 3: + +Input: K = 3, N = 14 +Output: 4 + + +Note: + +1 <= K <= 100 +1 <= N <= 10000 + + +``` + +## 思路 + +这是一道典型的动态规划题目,但是又和一般的动态规划不一样。 + +拿题目给的例子为例,两个鸡蛋,六层楼,我们最少扔几次? + +![887.super-egg-drop-1](../assets/problems/887.super-egg-drop-1.png) + +一个符合直觉的做法是,建立dp[i][j], 代表i个鸡蛋,j层楼最少扔几次,然后我们取dp[K][N]即可。 + +代码大概这样的: + +```js + const dp = Array(K + 1); + dp[0] = Array(N + 1).fill(0); + for (let i = 1; i < K + 1; i++) { + dp[i] = [0]; + for (let j = 1; j < N + 1; j++) { + // 只有一个鸡蛋 + if (i === 1) { + dp[i][j] = j; + continue; + } + // 只有一层楼 + if (j === 1) { + dp[i][j] = 1; + continue; + } + + // 每一层我们都模拟一遍 + const all = []; + for (let k = 1; k < j + 1; k++) { + const brokenCount = dp[i - 1][k - 1]; // 如果碎了 + const notBrokenCount = dp[i][j - k]; // 如果没碎 + all.push(Math.max(brokenCount, notBrokenCount)); // 最坏的可能 + } + dp[i][j] = Math.min(...all) + 1; // 最坏的集合中我们取最好的情况 + } + } + + return dp[K][N]; +``` + +果不其然,当我提交的时候,超时了。 这个的时复杂度是很高的,可以看到,我们内层暴力的求解所有可能,然后 +取最好的,这个过程非常耗时,大概是O(N^2 * K). + +然后我看了一位leetcode[网友](https://leetcode.com/lee215/)的回答, +他的想法是`dp[M][K]means that, given K eggs and M moves,what is the maximum number of floor that we can check.` + +我们按照他的思路重新建模: + +![887.super-egg-drop-2](../assets/problems/887.super-egg-drop-2.png) + +可以看到右下角的部分根本就不需要计算,从而节省很多时间 +## 关键点解析 + +- dp建模思路要发生变化, 即 +`dp[M][K]means that, given K eggs and M moves,what is the maximum number of floor that we can check.` + + +## 代码 + + +```js +/** + * @param {number} K + * @param {number} N + * @return {number} + */ +var superEggDrop = function(K, N) { + // 不选择dp[K][M]的原因是dp[M][K]可以简化操作 + const dp = Array(N + 1).fill(0).map(_ => Array(K + 1).fill(0)) + + let m = 0; + while (dp[m][K] < N) { + m++; + for (let k = 1; k <= K; ++k) + dp[m][k] = dp[m - 1][k - 1] + 1 + dp[m - 1][k]; + } + return m; +}; +``` diff --git a/problems/895.maximum-frequency-stack.md b/problems/895.maximum-frequency-stack.md new file mode 100644 index 0000000..b7d4eb8 --- /dev/null +++ b/problems/895.maximum-frequency-stack.md @@ -0,0 +1,114 @@ +## 题目地址(895. 最大频率栈) + +https://leetcode-cn.com/problems/maximum-frequency-stack/ + +## 题目描述 + +``` +实现 FreqStack,模拟类似栈的数据结构的操作的一个类。 + +FreqStack 有两个函数: + +push(int x),将整数 x 推入栈中。 +pop(),它移除并返回栈中出现最频繁的元素。 +如果最频繁的元素不只一个,则移除并返回最接近栈顶的元素。 +  + +示例: + +输入: +["FreqStack","push","push","push","push","push","push","pop","pop","pop","pop"], +[[],[5],[7],[5],[7],[4],[5],[],[],[],[]] +输出:[null,null,null,null,null,null,null,5,7,5,4] +解释: +执行六次 .push 操作后,栈自底向上为 [5,7,5,7,4,5]。然后: + +pop() -> 返回 5,因为 5 是出现频率最高的。 +栈变成 [5,7,5,7,4]。 + +pop() -> 返回 7,因为 5 和 7 都是频率最高的,但 7 最接近栈顶。 +栈变成 [5,7,5,4]。 + +pop() -> 返回 5 。 +栈变成 [5,7,4]。 + +pop() -> 返回 4 。 +栈变成 [5,7]。 +  + +提示: + +对 FreqStack.push(int x) 的调用中 0 <= x <= 10^9。 +如果栈的元素数目为零,则保证不会调用  FreqStack.pop()。 +单个测试样例中,对 FreqStack.push 的总调用次数不会超过 10000。 +单个测试样例中,对 FreqStack.pop 的总调用次数不会超过 10000。 +所有测试样例中,对 FreqStack.push 和 FreqStack.pop 的总调用次数不会超过 150000。 + +``` + +## 思路 + +我们以题目给的例子来讲解。 + +- 使用fraq 来存储对应的数字出现次数。key 是数字,value频率 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gda26lkj3aj30d00la76l.jpg) + +- 由于题目限制“如果最频繁的元素不只一个,则移除并返回最接近栈顶的元素。”,我们考虑使用栈来维护一个频率表 fraq_stack。key是频率,value是数字组成的栈。 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gda28hw3gaj30k20i8n10.jpg) + +- 同时用max_fraq 记录当前的最大频率值。 + +- 第一次pop的时候,我们最大的频率是3。由fraq_stack 知道我们需要pop掉5。 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gda2aojd9pj31160nadmq.jpg) + +- 之后pop依次是这样的(红色数字表示顺序) + +![](https://tva1.sinaimg.cn/large/00831rSTly1gda2ci160nj30pk0ki42y.jpg) + +## 关键点解析 + +- 栈的基本性质 +- hashtable的基本性质 +- push和pop的时候同时更新fraq,max_fraq 和 fraq_stack。 + +## 代码 + +```python +class FreqStack: + + def __init__(self): + self.fraq = collections.defaultdict(lambda: 0) + self.fraq_stack = collections.defaultdict(list) + self.max_fraq = 0 + + def push(self, x: int) -> None: + self.fraq[x] += 1 + if self.fraq[x] > self.max_fraq: + self.max_fraq = self.fraq[x] + self.fraq_stack[self.fraq[x]].append(x) + + def pop(self) -> int: + ans = self.fraq_stack[self.max_fraq].pop() + self.fraq[ans] -= 1 + if not self.fraq_stack[self.max_fraq]: + self.max_fraq -= 1 + return ans + +# Your FreqStack object will be instantiated and called as such: +# obj = FreqStack() +# obj.push(x) +# param_2 = obj.pop() +``` + +***复杂度分析*** +- 时间复杂度:push 和 pop 平均时间复杂度是 $O(1)$ +- 空间复杂度:$O(N)$,其中N为数字的总数。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) + + diff --git a/problems/90.subsets-ii-en.md b/problems/90.subsets-ii-en.md new file mode 100644 index 0000000..a17cb31 --- /dev/null +++ b/problems/90.subsets-ii-en.md @@ -0,0 +1,166 @@ +## Problem Link + +https://leetcode.com/problems/subsets-ii/description/ + +## Description +``` +Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set). + +Note: The solution set must not contain duplicate subsets. + +Example: + +Input: [1,2,2] +Output: +[ + [2], + [1], + [1,2,2], + [2,2], + [1,2], + [] +] + +``` + +## Solution + +Since this problem is seeking `Subset` not `Extreme Value`, dynamic programming is not an ideal solution. Other approaches should be taken into our consideration. + +Actually, there is a general approach to solve problems similar to this one -- backtracking. Given a [Code Template](https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)) here, it demonstrates how backtracking works with varieties of problems. Apart from current one, many problems can be solved by such a general approach. For more details, please check the `Related Problems` section below. + +Given a picture as followed, let's start with problem-solving ideas of this general solution. + +![backtrack](../assets/problems/backtrack.png) + +See Code Template details below. + +## Key Points + +- Backtrack Approach +- Backtrack Code Template/ Formula + +## Code + +* Supported Language:JS,C++,Python3 + +JavaScript Code: + +```js + + +/* + * @lc app=leetcode id=90 lang=javascript + * + * [90] Subsets II + * + * https://leetcode.com/problems/subsets-ii/description/ + * + * algorithms + * Medium (41.53%) + * Total Accepted: 197.1K + * Total Submissions: 469.1K + * Testcase Example: '[1,2,2]' + * + * Given a collection of integers that might contain duplicates, nums, return + * all possible subsets (the power set). + * + * Note: The solution set must not contain duplicate subsets. + * + * Example: + * + * + * Input: [1,2,2] + * Output: + * [ + * ⁠ [2], + * ⁠ [1], + * ⁠ [1,2,2], + * ⁠ [2,2], + * ⁠ [1,2], + * ⁠ [] + * ] + * + * + */ +function backtrack(list, tempList, nums, start) { + list.push([...tempList]); + for(let i = start; i < nums.length; i++) { + //nums can be duplicated, which is different from Problem 78 - subsets + //So the situation should be taken into consideration + if (i > start && nums[i] === nums[i - 1]) continue; + tempList.push(nums[i]); + backtrack(list, tempList, nums, i + 1) + tempList.pop(); + } +} +/** + * @param {number[]} nums + * @return {number[][]} + */ +var subsetsWithDup = function(nums) { + const list = []; + backtrack(list, [], nums.sort((a, b) => a - b), 0, []) + return list; +}; +``` +C++ Code: + +```C++ +class Solution { +private: + void subsetsWithDup(vector& nums, size_t start, vector& tmp, vector>& res) { + res.push_back(tmp); + for (auto i = start; i < nums.size(); ++i) { + if (i > start && nums[i] == nums[i - 1]) continue; + tmp.push_back(nums[i]); + subsetsWithDup(nums, i + 1, tmp, res); + tmp.pop_back(); + } + } +public: + vector> subsetsWithDup(vector& nums) { + auto tmp = vector(); + auto res = vector>(); + sort(nums.begin(), nums.end()); + subsetsWithDup(nums, 0, tmp, res); + return res; + } +}; +``` +Python Code: + +```Python +class Solution: + def subsetsWithDup(self, nums: List[int], sorted: bool=False) -> List[List[int]]: + """Backtrack Approach: by sorting parameters first to avoid repeting sort later""" + if not nums: + return [[]] + elif len(nums) == 1: + return [[], nums] + else: + # Sorting first to filter duplicated numbers + # Note,this problem takes higher time complexity + # So, it could greatly improve time efficiency by adding one parameter to avoid repeting sort in following procedures + if not sorted: + nums.sort() + # Backtrack Approach + pre_lists = self.subsetsWithDup(nums[:-1], sorted=True) + all_lists = [i+[nums[-1]] for i in pre_lists] + pre_lists + # distinct elements + result = [] + for i in all_lists: + if i not in result: + result.append(i) + return result +``` + +## Related Problems + +- [39.combination-sum](./39.combination-sum.md)(chinese) +- [40.combination-sum-ii](./40.combination-sum-ii.md)(chinese) +- [46.permutations](./46.permutations.md)(chinese) +- [47.permutations-ii](./47.permutations-ii.md)(chinese) +- [78.subsets](./78.subsets-en.md) +- [113.path-sum-ii](./113.path-sum-ii.md)(chinese) +- [131.palindrome-partitioning](./131.palindrome-partitioning.md)(chinese) diff --git a/problems/90.subsets-ii.md b/problems/90.subsets-ii.md new file mode 100644 index 0000000..36c2e84 --- /dev/null +++ b/problems/90.subsets-ii.md @@ -0,0 +1,174 @@ + +## 题目地址 +https://leetcode.com/problems/subsets-ii/description/ + +## 题目描述 +``` +Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set). + +Note: The solution set must not contain duplicate subsets. + +Example: + +Input: [1,2,2] +Output: +[ + [2], + [1], + [1,2,2], + [2,2], + [1,2], + [] +] + +``` + +## 思路 + +这道题目是求集合,并不是`求极值`,因此动态规划不是特别切合,因此我们需要考虑别的方法。 + +这种题目其实有一个通用的解法,就是回溯法。 +网上也有大神给出了这种回溯法解题的 +[通用写法](https://leetcode.com/problems/combination-sum/discuss/16502/A-general-approach-to-backtracking-questions-in-Java-(Subsets-Permutations-Combination-Sum-Palindrome-Partitioning)),这里的所有的解法使用通用方法解答。 +除了这道题目还有很多其他题目可以用这种通用解法,具体的题目见后方相关题目部分。 + +我们先来看下通用解法的解题思路,我画了一张图: + +![backtrack](../assets/problems/backtrack.png) + +通用写法的具体代码见下方代码区。 + +## 关键点解析 + +- 回溯法 +- backtrack 解题公式 + + +## 代码 + +* 语言支持:JS,C++,Python3 + +JavaScript Code: + +```js + + +/* + * @lc app=leetcode id=90 lang=javascript + * + * [90] Subsets II + * + * https://leetcode.com/problems/subsets-ii/description/ + * + * algorithms + * Medium (41.53%) + * Total Accepted: 197.1K + * Total Submissions: 469.1K + * Testcase Example: '[1,2,2]' + * + * Given a collection of integers that might contain duplicates, nums, return + * all possible subsets (the power set). + * + * Note: The solution set must not contain duplicate subsets. + * + * Example: + * + * + * Input: [1,2,2] + * Output: + * [ + * ⁠ [2], + * ⁠ [1], + * ⁠ [1,2,2], + * ⁠ [2,2], + * ⁠ [1,2], + * ⁠ [] + * ] + * + * + */ +function backtrack(list, tempList, nums, start) { + list.push([...tempList]); + for(let i = start; i < nums.length; i++) { + // 和78.subsets的区别在于这道题nums可以有重复 + // 因此需要过滤这种情况 + if (i > start && nums[i] === nums[i - 1]) continue; + tempList.push(nums[i]); + backtrack(list, tempList, nums, i + 1) + tempList.pop(); + } +} +/** + * @param {number[]} nums + * @return {number[][]} + */ +var subsetsWithDup = function(nums) { + const list = []; + backtrack(list, [], nums.sort((a, b) => a - b), 0, []) + return list; +}; +``` +C++ Code: + +```C++ +class Solution { +private: + void subsetsWithDup(vector& nums, size_t start, vector& tmp, vector>& res) { + res.push_back(tmp); + for (auto i = start; i < nums.size(); ++i) { + if (i > start && nums[i] == nums[i - 1]) continue; + tmp.push_back(nums[i]); + subsetsWithDup(nums, i + 1, tmp, res); + tmp.pop_back(); + } + } +public: + vector> subsetsWithDup(vector& nums) { + auto tmp = vector(); + auto res = vector>(); + sort(nums.begin(), nums.end()); + subsetsWithDup(nums, 0, tmp, res); + return res; + } +}; +``` +Python Code: + +```Python +class Solution: + def subsetsWithDup(self, nums: List[int], sorted: bool=False) -> List[List[int]]: + """回溯法,通过排序参数避免重复排序""" + if not nums: + return [[]] + elif len(nums) == 1: + return [[], nums] + else: + # 先排序,以便去重 + # 注意,这道题排序花的时间比较多 + # 因此,增加一个参数,使后续过程不用重复排序,可以大幅提高时间效率 + if not sorted: + nums.sort() + # 回溯法 + pre_lists = self.subsetsWithDup(nums[:-1], sorted=True) + all_lists = [i+[nums[-1]] for i in pre_lists] + pre_lists + # 去重 + result = [] + for i in all_lists: + if i not in result: + result.append(i) + return result +``` + +## 相关题目 + +- [39.combination-sum](./39.combination-sum.md) +- [40.combination-sum-ii](./40.combination-sum-ii.md) +- [46.permutations](./46.permutations.md) +- [47.permutations-ii](./47.permutations-ii.md) +- [78.subsets](./78.subsets.md) +- [113.path-sum-ii](./113.path-sum-ii.md) +- [131.palindrome-partitioning](./131.palindrome-partitioning.md) + + + + diff --git a/problems/900.rle-iterator.md b/problems/900.rle-iterator.md new file mode 100644 index 0000000..0ad6267 --- /dev/null +++ b/problems/900.rle-iterator.md @@ -0,0 +1,110 @@ +## 题目地址 + +https://leetcode.com/problems/rle-iterator/description/ + +## 题目描述 + +``` +Write an iterator that iterates through a run-length encoded sequence. + +The iterator is initialized by RLEIterator(int[] A), where A is a run-length encoding of some sequence. More specifically, for all even i, A[i] tells us the number of times that the non-negative integer value A[i+1] is repeated in the sequence. + +The iterator supports one function: next(int n), which exhausts the next n elements (n >= 1) and returns the last element exhausted in this way. If there is no element left to exhaust, next returns -1 instead. + +For example, we start with A = [3,8,0,9,2,5], which is a run-length encoding of the sequence [8,8,8,5,5]. This is because the sequence can be read as "three eights, zero nines, two fives". + + + +Example 1: + +Input: ["RLEIterator","next","next","next","next"], [[[3,8,0,9,2,5]],[2],[1],[1],[2]] +Output: [null,8,8,5,-1] +Explanation: +RLEIterator is initialized with RLEIterator([3,8,0,9,2,5]). +This maps to the sequence [8,8,8,5,5]. +RLEIterator.next is then called 4 times: + +.next(2) exhausts 2 terms of the sequence, returning 8. The remaining sequence is now [8, 5, 5]. + +.next(1) exhausts 1 term of the sequence, returning 8. The remaining sequence is now [5, 5]. + +.next(1) exhausts 1 term of the sequence, returning 5. The remaining sequence is now [5]. + +.next(2) exhausts 2 terms, returning -1. This is because the first term exhausted was 5, +but the second term did not exist. Since the last term exhausted does not exist, we return -1. + +Note: + +0 <= A.length <= 1000 +A.length is an even integer. +0 <= A[i] <= 10^9 +There are at most 1000 calls to RLEIterator.next(int n) per test case. +Each call to RLEIterator.next(int n) will have 1 <= n <= 10^9. + +``` + +## 思路 + +这是一个游程编码的典型题目。 + +该算法分为两个部分,一个是初始化,一个是调用`next(n)`. + +我们需要做的就是初始化的时候,记住这个A。 然后每次调用`next(n)`的时候只需要 + +判断n是否大于A[i](i从0开始) + +- 如果大于A[i], 那就说明不够,我们移除数组前两项,更新n,重复1 + +- 如果小于A[i], 则说明够了,更新A[i] + +这样做,我们每次都要更新A,还有一种做法就是不更新A,而是`伪更新`,即用一个变量记录,当前访问到的数组位置。 + +> 很多时候我们需要原始的,那么就必须这种放了,我的解法就是这种方法。 + + +## 关键点解析 + + + +## 代码 + +```js +/** + * @param {number[]} A + */ +var RLEIterator = function(A) { + this.A = A; + this.current = 0; +}; + + +/** + * @param {number} n + * @return {number} + */ +RLEIterator.prototype.next = function(n) { + const A = this.A; + while(this.current < A.length && A[this.current] < n){ + n = n - A[this.current]; + this.current += 2; + } + + if(this.current >= A.length){ + return -1; + } + + A[this.current] = A[this.current] - n; // 更新Count + return A[this.current + 1]; // 返回element +}; + +/** + * Your RLEIterator object will be instantiated and called as such: + * var obj = new RLEIterator(A) + * var param_1 = obj.next(n) + */ +``` + +## 扩展阅读 + +[哈夫曼编码和游程编码](../thinkings/run-length-encode-and-huffman-encode.md) + diff --git a/problems/91.decode-ways.md b/problems/91.decode-ways.md new file mode 100644 index 0000000..c7bb381 --- /dev/null +++ b/problems/91.decode-ways.md @@ -0,0 +1,129 @@ + +## 题目地址 +https://leetcode.com/problems/decode-ways/description/ + +## 题目描述 +``` +A message containing letters from A-Z is being encoded to numbers using the following mapping: + +'A' -> 1 +'B' -> 2 +... +'Z' -> 26 +Given a non-empty string containing only digits, determine the total number of ways to decode it. + +Example 1: + +Input: "12" +Output: 2 +Explanation: It could be decoded as "AB" (1 2) or "L" (12). +Example 2: + +Input: "226" +Output: 3 +Explanation: It could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6). +``` + +## 思路 + +这道题目和爬楼梯问题有异曲同工之妙。 + +这也是一道典型的动态规划题目。我们来思考: + +- 对于一个数字来说[1,9]这九个数字能够被识别为一种编码方式 +- 对于两个数字来说[10, 26]这几个数字能被识别为一种编码方式 + +我们考虑用dp[i]来切分子问题, 那么dp[i]表示的意思是当前字符串的以索引i结尾的子问题。 +这样的话,我们最后只需要取dp[s.length] 就可以解决问题了。 + +关于递归公式,让我们`先局部后整体`。对于局部,我们遍历到一个元素的时候, +我们有两种方式来组成编码方式,第一种是这个元素本身(需要自身是[1,9]), +第二种是它和前一个元素组成[10, 26]。 用伪代码来表示的话就是: +`dp[i] = 以自身去编码(一位) + 以前面的元素和自身去编码(两位)` .这显然是完备的, +这样我们通过层层推导就可以得到结果。 + + +## 关键点解析 + +- 爬楼梯问题(我把这种题目统称为爬楼梯问题) + + +## 代码 + +```js + + +/* + * @lc app=leetcode id=91 lang=javascript + * + * [91] Decode Ways + * + * https://leetcode.com/problems/decode-ways/description/ + * + * algorithms + * Medium (21.93%) + * Total Accepted: 254.4K + * Total Submissions: 1.1M + * Testcase Example: '"12"' + * + * A message containing letters from A-Z is being encoded to numbers using the + * following mapping: + * + * + * 'A' -> 1 + * 'B' -> 2 + * ... + * 'Z' -> 26 + * + * + * Given a non-empty string containing only digits, determine the total number + * of ways to decode it. + * + * Example 1: + * + * + * Input: "12" + * Output: 2 + * Explanation: It could be decoded as "AB" (1 2) or "L" (12). + * + * + * Example 2: + * + * + * Input: "226" + * Output: 3 + * Explanation: It could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 + * 6). + * + */ +/** + * @param {string} s + * @return {number} + */ +var numDecodings = function(s) { + if (s == null || s.length == 0) { + return 0; + } + const dp = Array(s.length + 1).fill(0); + dp[0] = 1; + dp[1] = s[0] !== "0" ? 1 : 0; + for (let i = 2; i < s.length + 1; i++) { + const one = +s.slice(i - 1, i); + const two = +s.slice(i - 2, i); + + if (two >= 10 && two <= 26) { + dp[i] = dp[i - 2]; + } + + if (one >= 1 && one <= 9) { + dp[i] += dp[i - 1]; + } + } + + return dp[dp.length - 1]; +}; +``` + +## 扩展 + +如果编码的范围不再是1-26,而是三位的话怎么办? diff --git a/problems/912.sort-an-array.md b/problems/912.sort-an-array.md new file mode 100644 index 0000000..f98daf5 --- /dev/null +++ b/problems/912.sort-an-array.md @@ -0,0 +1,154 @@ + +## 题目地址 +https://leetcode.com/problems/sort-an-array/ + +## 题目描述 +``` +Given an array of integers nums, sort the array in ascending order. + + + +Example 1: + +Input: [5,2,3,1] +Output: [1,2,3,5] +Example 2: + +Input: [5,1,1,2,0,0] +Output: [0,0,1,1,2,5] + + +Note: + +1 <= A.length <= 10000 +-50000 <= A[i] <= 50000 +``` + +## 思路 + +这是一个很少见的直接考察`排序`的题目。 其他题目一般都是暗含`排序`,这道题则简单粗暴,直接让你排序。 +并且这道题目的难度是`Medium`, 笔者感觉有点不可思议。 + +我们先来看题目的限制条件,这其实在选择算法的过程中是重要的。 看到这道题的时候,大脑就闪现出了各种排序算法, +这也算是一个复习`排序算法`的机会吧。 + +题目的限制条件是有两个,第一是元素个数不超过10k,这个不算大。 另外一个是数组中的每一项范围都是`-50k`到`50k`(包含左右区间)。 +看到这里,基本我就排除了时间复杂度为O(n^2)的算法。 + +> 我没有试时间复杂度 O(n^2) 的解法,大家可以试一下,看是不是会TLE。 + +剩下的就是基于比较的`nlogn`算法,以及基于特定条件的O(n)算法。 + +由于平时很少用到`计数排序`等O(n)的排序算法,一方面是空间复杂度不是常量,另一方面是其要求数据范围不是很大才行,不然会浪费很多空间。 +但是这道题我感觉可以试一下。 在这里,我用了两种方法,一种是`计数排序`,一种是`快速排序`来解决。 大家也可以尝试用别的解法来解决。 + +### 解法一 - 计数排序 + +时间复杂度O(n)空间复杂度O(m) m 为数组中值的取值范围,在这道题就是`50000 * 2 + 1`。 + +我们只需要准备一个数组取值范围的数字,然后遍历一遍,将每一个元素放到这个数组对应位置就好了, +放的规则是`索引为数字的值,value为出现的次数`。 + +这样一次遍历,我们统计出了所有的数字出现的位置和次数。 我们再来一次遍历,将其输出到即可。 + +![sort-an-array-1](../assets/problems/912.sort-an-array-1.png) + + +### 解法二 - 快速排序 + +快速排序和归并排序都是分支思想来进行排序的算法, 并且二者都非常流行。 快速排序的核心点在于选择轴元素。 + +每次我们将数组分成两部分,一部分是比pivot(轴元素)大的,另一部分是不比pivot大的。 我们不断重复这个过程, +直到问题的规模缩小的寻常(即只有一个元素的情况)。 + +快排的核心点在于如何选择轴元素,一般而言,选择轴元素有三种策略: + +- 数组最左边的元素 +- 数组最右边的元素 +- 数组中间的元素(我采用的是这种,大家可以尝试下别的) +- 数组随机一项元素 + + +![sort-an-array-2](../assets/problems/912.sort-an-array-2.png) + +(图片来自: https://www.geeksforgeeks.org/quick-sort/) + +> 图片中的轴元素是最后面的元素,而提供的解法是中间元素,这点需要注意,但是这并不影响理解。 + + +## 关键点解析 + +- 排序算法 +- 注意题目的限制条件从而选择合适的算法 + + +## 代码 + +计数排序: + +代码支持: JavaScript +```js +/** + * @param {number[]} nums + * @return {number[]} + */ +var sortArray = function(nums) { + const counts = Array(50000 * 2 + 1).fill(0); + const res = []; + for(const num of nums) counts[50000 + num] += 1; + for(let i in counts) { + while(counts[i]--) { + res.push(i - 50000) + } + } + return res; +}; +``` + + +快速排序: + +代码支持: JavaScript +```js +function swap(nums, a, b) { + const temp = nums[a]; + nums[a] = nums[b]; + nums[b] = temp; +} + +function helper(nums, start, end) { + if (start >= end) return; + const pivotIndex = start + ((end - start) >>> 1) + const pivot = nums[pivotIndex] + let i = start; + let j = end; + while (i <= j) { + while (nums[i] < pivot) i++; + while (nums[j] > pivot) j--; + if (i <= j) { + swap(nums, i, j); + i++; + j--; + } + } + helper(nums, start, j); + helper(nums, i, end); +} + +/** + * @param {number[]} nums + * @return {number[]} + */ +var sortArray = function(nums) { + helper(nums, 0, nums.length - 1); + return nums; +}; +``` + +## 扩展 + +- 你是否可以用其他方式排序算法解决 + +## 参考 + +- [QuickSort - geeksforgeeks](https://www.geeksforgeeks.org/quick-sort/) diff --git a/problems/92.reverse-linked-list-ii.md b/problems/92.reverse-linked-list-ii.md new file mode 100644 index 0000000..3278c21 --- /dev/null +++ b/problems/92.reverse-linked-list-ii.md @@ -0,0 +1,289 @@ +## 题目地址 +https://leetcode.com/problems/reverse-linked-list-ii/description/ + +## 题目描述 +Reverse a linked list from position m to n. Do it in one-pass. + +Note: 1 ≤ m ≤ n ≤ length of list. + +Example: + +Input: 1->2->3->4->5->NULL, m = 2, n = 4 +Output: 1->4->3->2->5->NULL + +## 思路 + +这道题和[206.reverse-linked-list](https://github.com/azl397985856/leetcode/blob/master/problems/206.reverse-linked-list.md) 有点类似,并且这道题是206的升级版。 让我们反转某一个区间,而不是整个链表,我们可以将206看作本题的特殊情况(special case)。 + +核心在于**取出需要反转的这一小段链表,反转完后再插入到原先的链表中。** + +以本题为例: + +反转的是2,3,4这三个点,那么我们可以先取出2,用cur指针指向2,然后当取出3的时候,我们将3指向2的,把cur指针前移到3,依次类推,到4后停止,这样我们得到一个新链表4->3->2, cur指针指向4。 + +对于原链表来说,有两个点的位置很重要,需要用指针记录下来,分别是1和5,把新链表插入的时候需要这两个点的位置。用pre指针记录1的位置当4结点被取走后,5的位置需要记下来 + +这样我们就可以把反转后的那一小段链表加入到原链表中 + +![92.reverse-linked-list-ii](../assets/92.reverse-linked-list-ii.gif) + +(图片来自: https://github.com/MisterBooo/LeetCodeAnimation) + + +首先我们直接返回head是不行的。 当 m 不等于1的时候是没有问题的,但只要 m 为1,就会有问题。 + +```python +class Solution: + def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode: + pre = None + cur = head + i = 0 + p1 = p2 = p3 = p4 = None + # ... + if p1: + p1.next = p3 + else: + dummy.next = p3 + if p2: + p2.next = p4 + return head +``` + + 如上代码是不可以的,我们考虑使用dummy节点。 + ```python + class Solution: + def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode: + pre = None + cur = head + i = 0 + p1 = p2 = p3 = p4 = None + dummy = ListNode(0) + dummy.next = head + + # ... + + if p1: + p1.next = p3 + else: + dummy.next = p3 + if p2: + p2.next = p4 + + return dummy.next + ``` + + 关于链表反转部分, 顺序比较重要,我们需要: + + - 先 cur.next = pre + - 再 更新p2和p2.next(其中要设置p2.next = None,否则会互相应用,造成无限循环) + - 最后更新 pre 和 cur + + 上述的顺序不能错,不然会有问题。原因就在于`p2.next = None`,如果这个放在最后,那么我们的cur会提前断开。 + + ```python + while cur: + i += 1 + if i == m - 1: + p1 = cur + next = cur.next + if m < i <= n: + cur.next = pre + + if i == m: + p2 = cur + p2.next = None + + if i == n: + p3 = cur + + if i == n + 1: + p4 = cur + + pre = cur + cur = next + ``` + +## 关键点解析 + +- 链表的基本操作 +- 考虑特殊情况 m 是 1 或者 n是链表长度的情况,我们可以采用虚拟节点dummy 简化操作 +- 用四个变量记录特殊节点, 然后操作这四个节点使之按照一定方式连接即可。 +- 注意更新current和pre的位置, 否则有可能出现溢出 + + +## 代码 + +语言支持:JS, C++, Python3 + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=92 lang=javascript + * + * [92] Reverse Linked List II + * + * https://leetcode.com/problems/reverse-linked-list-ii/description/ + */ +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} head + * @param {number} m + * @param {number} n + * @return {ListNode} + */ +var reverseBetween = function(head, m, n) { + // 虚拟节点,简化操作 + const dummyHead = { + next: head + } + + let cur = dummyHead.next; // 当前遍历的节点 + let pre = cur; // 因为要反转,因此我们需要记住前一个节点 + let index = 0; // 链表索引,用来判断是否是特殊位置(头尾位置) + + // 上面提到的四个特殊节点 + let p1 = p2 = p3 = p4 = null + + while(cur) { + const next = cur.next; + index++; + + // 对 (m - n) 范围内的节点进行反转 + if (index > m && index <= n) { + cur.next = pre; + } + + // 下面四个if都是边界, 用于更新四个特殊节点的值 + if (index === m - 1) { + p1 = cur; + } + if (index === m) { + p2 = cur; + } + + if (index === n) { + p3 = cur; + } + + if (index === n + 1) { + p4 = cur;; + } + + pre = cur; + + cur = next; + } + + // 两个链表合并起来 + (p1 || dummyHead).next = p3; // 特殊情况需要考虑 + p2.next = p4; + + return dummyHead.next; +}; + +``` +C++ Code: +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +class Solution { +public: + ListNode* reverseBetween(ListNode* head, int s, int e) { + if (s == e) return head; + ListNode* prev = nullptr; + auto cur = head; + for (int i = 1; i < s; ++i) { + prev = cur; + cur = cur->next; + } + // 此时各指针指向: + // x -> x -> x -> x -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> x -> x -> x -> + // ^head ^prev ^cur + ListNode* p = nullptr; + auto c = cur; + auto tail = c; + ListNode* n = nullptr; + for (int i = s; i <= e; ++i) { + n = c->next; + c->next = p; + p = c; + c = n; + } + // 此时各指针指向: + // x -> x -> x -> x 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1 x -> x -> x -> + // ^head ^prev ^p ^cur ^c + // ^tail + if (prev != nullptr) { // 若指向前一个节点的指针不为空,则说明s在链表中间(不是头节点) + prev->next = p; + cur->next = c; + return head; + } else { + if (tail != nullptr) tail->next = c; + return p; + } + } +}; +``` +Python Code: +```Python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode: + """采用先翻转中间部分,之后与不变的前后部分拼接的思路""" + # 处理特殊情况 + if m == n: + return head + + # 例行性的先放一个开始节点,方便操作 + first = ListNode(0) + first.next = head + + # 通过以下两个节点记录拼接点 + before_m = first # 原链表m前的部分 + after_n = None # 原链表n后的部分 + + # 通过以下两个节点记录翻转后的链表 + between_mn_head = None + between_mn_end = None + + index = 0 + cur_node = first + while index < n: + index += 1 + cur_node = cur_node.next + if index == m - 1: + before_m = cur_node + elif index == m: + between_mn_end = ListNode(cur_node.val) + between_mn_head = between_mn_end + elif index > m: + temp = between_mn_head + between_mn_head = ListNode(cur_node.val) + between_mn_head.next = temp + if index == n: + after_n = cur_node.next + + # 进行拼接 + between_mn_end.next = after_n + before_m.next = between_mn_head + + return first.next +``` diff --git a/problems/935.knight-dialer.md b/problems/935.knight-dialer.md new file mode 100644 index 0000000..1e4aa39 --- /dev/null +++ b/problems/935.knight-dialer.md @@ -0,0 +1,115 @@ + +## 题目地址 (935. 骑士拨号器) +https://leetcode-cn.com/problems/knight-dialer/ + +## 题目描述 +``` +国际象棋中的骑士可以按下图所示进行移动: + +``` + +![](https://tva1.sinaimg.cn/large/00831rSTly1gcgzhjw437j305305p3yi.jpg) + +```           + +这一次,我们将 “骑士” 放在电话拨号盘的任意数字键(如上图所示)上,接下来,骑士将会跳 N-1 步。每一步必须是从一个数字键跳到另一个数字键。 + +每当它落在一个键上(包括骑士的初始位置),都会拨出键所对应的数字,总共按下 N 位数字。 + +你能用这种方式拨出多少个不同的号码? + +因为答案可能很大,所以输出答案模 10^9 + 7。 + +  + +示例 1: + +输入:1 +输出:10 +示例 2: + +输入:2 +输出:20 +示例 3: + +输入:3 +输出:46 +  + +提示: + +1 <= N <= 5000 + +``` + +## 深度优先遍历(DFS) + +### 思路 + +这道题要求解一个数字。并且每一个格子能够跳的状态是确定的。 因此我们的思路就是“状态机”(动态规划),暴力遍历(BFS or DFS),这里我们使用 DFS。(注意这几种思路并无本质不同) + +对于每一个号码键盘,我们可以转移的状态是确定的,我们做一个”预处理“,将这些状态转移记录到一个数组 jump,其中 jump[i] 表示 i 位置可以跳的点(用一个数组来表示)。问题转化为: + +- 从 0 开始所有的路径 +- 从 1 开始所有的路径 +- 从 2 开始所有的路径 +- ... +- 从 9 开始所有的路径 + +不管从几开始思路都是一样的。 我们使用一个函数 f(i, n) 表示`骑士在 i 的位置,还剩下 N 步可以走`的时候可以拨出的总的号码数。那么问题就是求解 `f(0, N) + f(1, N) + f(2, N) + ... + f(9, N)`。对于 f(i, n),我们初始化 cnt 为 0,由于 i 能跳的格子是 jump[i],我们将其 `cnt += f(j, n - 1)`,其中 j 属于 jump[i],最终返回 cnt 即可。 + +不难看出,这种算法有大量重复计算,我们使用记忆化递归形式来减少重复计算。 这种算法勉强通过。 + +### 代码 + +```python +class Solution: + def knightDialer(self, N: int) -> int: + cnt = 0 + jump = [[4, 6], [6, 8], [7, 9], [4, 8], [ + 0, 3, 9], [], [0, 1, 7], [2, 6], [1, 3], [2, 4]] + visited = dict() + + def helper(i, n): + if (i, n) in visited: return visited[(i, n)] + if n == 1: + return 1 + cnt = 0 + for j in jump[i]: + cnt += helper(j, n - 1) + visited[(i, n)] = cnt + return cnt + for i in range(10): + cnt += helper(i, N) + return cnt % (10**9 + 7) +``` + +**复杂度分析** +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(N)$ + +## 朴素遍历 + +### 思路 + +我们使用迭代的形式来优化上述过程。我们初始化十个变量分别表示键盘不同位置能够拨出的号码数,并且初始化为 1。接下来我们只要循环 N - 1 次,不断更新状态即可。不过这种算法和上述算法并无本质不同。 + +### 代码 + +```python +class Solution: + def knightDialer(self, N: int) -> int: + a0 = a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = 1 + for _ in range(N - 1): + a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = a4 + a6, a6 + a8, a7 + \ + a9, a4 + a8, a0 + a3 + a9, 0, a0 + a1 + a7, a2 + a6, a1 + a3, a2 + a4 + return (a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9) % (10**9 + 7) +``` + +**复杂度分析** +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(1)$ + +欢迎关注我的公众号《脑洞前端》获取更多更新鲜的 LeetCode 题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) diff --git a/problems/94.binary-tree-inorder-traversal.md b/problems/94.binary-tree-inorder-traversal.md new file mode 100644 index 0000000..4c8c02a --- /dev/null +++ b/problems/94.binary-tree-inorder-traversal.md @@ -0,0 +1,270 @@ +## 题目地址 +https://leetcode.com/problems/binary-tree-inorder-traversal/description/ + +## 题目描述 +``` +Given a binary tree, return the inorder traversal of its nodes' values. + +Example: + +Input: [1,null,2,3] + 1 + \ + 2 + / + 3 + +Output: [1,3,2] +Follow up: Recursive solution is trivial, could you do it iteratively? +``` +## 思路 + +递归的方式相对简单,非递归的方式借助栈这种数据结构实现起来会相对轻松。 + +如果采用非递归,可以用栈(Stack)的思路来处理问题。 + +中序遍历的顺序为左-根-右,具体算法为: + +- 从根节点开始,先将根节点压入栈 + +- 然后再将其所有左子结点压入栈,取出栈顶节点,保存节点值 + +- 再将当前指针移到其右子节点上,若存在右子节点,则在下次循环时又可将其所有左子结点压入栈中, 重复上步骤 + +![94.binary-tree-inorder-traversal](../assets/94.binary-tree-inorder-traversal.gif) + +(图片来自: https://github.com/MisterBooo/LeetCodeAnimation) +## 关键点解析 + +- 二叉树的基本操作(遍历) +> 不同的遍历算法差异还是蛮大的 +- 如果非递归的话利用栈来简化操作 + +- 如果数据规模不大的话,建议使用递归 + +- 递归的问题需要注意两点,一个是终止条件,一个如何缩小规模 + +1. 终止条件,自然是当前这个元素是null(链表也是一样) + +2. 由于二叉树本身就是一个递归结构, 每次处理一个子树其实就是缩小了规模, +难点在于如何合并结果,这里的合并结果其实就是`left.concat(mid).concat(right)`, +mid是一个具体的节点,left和right`递归求出即可` + + +## 代码 + +* 语言支持:JS,C++,Python3, Java + +JavaScript Code: + +```js +/* + * @lc app=leetcode id=94 lang=javascript + * + * [94] Binary Tree Inorder Traversal + * + * https://leetcode.com/problems/binary-tree-inorder-traversal/description/ + * + * algorithms + * Medium (55.22%) + * Total Accepted: 422.4K + * Total Submissions: 762.1K + * Testcase Example: '[1,null,2,3]' + * + * Given a binary tree, return the inorder traversal of its nodes' values. + * + * Example: + * + * + * Input: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * Output: [1,3,2] + * + * Follow up: Recursive solution is trivial, could you do it iteratively? + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var inorderTraversal = function(root) { + // 1. Recursive solution + // if (!root) return []; + // const left = root.left ? inorderTraversal(root.left) : []; + // const right = root.right ? inorderTraversal(root.right) : []; + // return left.concat([root.val]).concat(right); + + // 2. iterative solutuon + if (!root) return []; + const stack = [root]; + const ret = []; + let left = root.left; + + let item = null; // stack 中弹出的当前项 + + while(left) { + stack.push(left); + left = left.left; + } + + while(item = stack.pop()) { + ret.push(item.val); + let t = item.right; + + while(t) { + stack.push(t); + t = t.left; + } + } + + return ret; + +}; + +``` +C++ Code: + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +public: + vector inorderTraversal(TreeNode* root) { + vector s; + vector v; + while (root != NULL || !s.empty()) { + for (; root != NULL; root = root->left) + s.push_back(root); + v.push_back(s.back()->val); + root = s.back()->right; + s.pop_back(); + } + return v; + } +}; +``` +Python Code: +```Python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + """ + 1. 递归法可以一行代码完成,无需讨论; + 2. 迭代法一般需要通过一个栈保存节点顺序,我们这里直接使用列表 + - 首先,我要按照中序遍历的顺序存入栈,这边用的逆序,方便从尾部开始处理 + - 在存入栈时加入一个是否需要深化的参数 + - 在回头取值时,这个参数应该是否,即直接取值 + - 简单调整顺序,即可实现前序和后序遍历 + """ + # 递归法 + # if root is None: + # return [] + # return self.inorderTraversal(root.left)\ + # + [root.val]\ + # + self.inorderTraversal(root.right) + # 迭代法 + result = [] + stack = [(1, root)] + while stack: + go_deeper, node = stack.pop() + if node is None: + continue + if go_deeper: + # 左右节点还需继续深化,并且入栈是先右后左 + stack.append((1, node.right)) + # 节点自身已遍历,回头可以直接取值 + stack.append((0, node)) + stack.append((1, node.left)) + else: + result.append(node.val) + return result +``` + +Java Code: + +- recursion + +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +class Solution { + List res = new LinkedList<>(); + public List inorderTraversal(TreeNode root) { + inorder(root); + return res; + } + + public void inorder (TreeNode root) { + if (root == null) return; + + inorder(root.left); + + res.add(root.val); + + inorder(root.right); + } +} +``` + +- iteration + +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +class Solution { + public List inorderTraversal(TreeNode root) { + List res = new ArrayList<> (); + Stack stack = new Stack<> (); + + while (root != null || !stack.isEmpty()) { + while (root != null) { + stack.push(root); + root = root.left; + } + root = stack.pop(); + res.add(root.val); + root = root.right; + } + return res; + } +} +``` diff --git a/problems/95.unique-binary-search-trees-ii.md b/problems/95.unique-binary-search-trees-ii.md new file mode 100644 index 0000000..603c8e4 --- /dev/null +++ b/problems/95.unique-binary-search-trees-ii.md @@ -0,0 +1,78 @@ +## 题目地址(95. 不同的二叉搜索树 II) + +https://leetcode-cn.com/problems/unique-binary-search-trees-ii/description/ + +## 题目描述 + +``` +给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。 + +示例: + +输入: 3 +输出: +[ +  [1,null,3,2], +  [3,2,null,1], +  [3,1,null,null,2], +  [2,1,3], +  [1,null,2,null,3] +] +解释: +以上的输出对应以下 5 种不同结构的二叉搜索树: + + 1 3 3 2 1 + \ / / / \ \ + 3 2 1 1 3 2 + / / \ \ + 2 1 2 3 + + +``` + +## 思路 + +这是一个经典的使用分治思路的题目。基本思路和[96.unique-binary-search-trees](./96.unique-binary-search-trees.md)一样。 + +只是我们需要求解的不仅仅是数字,而是要求解所有的组合。我们假设问题 f(1, n) 是求解 1 到 n(两端包含)的所有二叉树。那么我们的目标就是求解 f(1, n)。 + +我们将问题进一步划分为子问题,假如左侧和右侧的树分别求好了,我们是不是只要运用组合的原理,将左右两者进行做和就好了,我们需要两层循环来完成这个过程。 + +## 关键点解析 + +- 分治法 + +## 代码 + +- 语言支持:Python3 + +Python3 Code: + +```Python +class Solution: + def generateTrees(self, n: int) -> List[TreeNode]: + if not n: + return [] + + def generateTree(start, end): + if start > end: + return [None] + res = [] + for i in range(start, end + 1): + ls = generateTree(start, i - 1) + rs = generateTree(i + 1, end) + for l in ls: + for r in rs: + node = TreeNode(i) + node.left = l + node.right = r + res.append(node) + + return res + + return generateTree(1, n) +``` + +## 相关题目 + +- [96.unique-binary-search-trees](./96.unique-binary-search-trees.md) diff --git a/problems/96.unique-binary-search-trees.md b/problems/96.unique-binary-search-trees.md new file mode 100644 index 0000000..effd3bb --- /dev/null +++ b/problems/96.unique-binary-search-trees.md @@ -0,0 +1,88 @@ +## 题目地址(96. 不同的二叉搜索树) + +https://leetcode-cn.com/problems/unique-binary-search-trees-ii/description/ + +## 题目描述 + +``` +给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? + +示例: + +输入: 3 +输出: 5 +解释: +给定 n = 3, 一共有 5 种不同结构的二叉搜索树: + + 1 3 3 2 1 + \ / / / \ \ + 3 2 1 1 3 2 + / / \ \ + 2 1 2 3 + +``` + +## 思路 + +这是一个经典的使用分治思路的题目。 + +对于数字 n ,我们可以 1- n 这样的离散整数分成左右两部分。我们不妨设其分别为 A 和 B。那么问题转化为 A 和 B 所能组成的 BST 的数量的笛卡尔积。而对于 A 和 B 以及原问题除了规模,没有不同,这不就是分治思路么?至于此,我们只需要考虑边界即可,边界很简单就是 n 小于等于 1 的时候,我们返回 1。 + +具体来说: + +- 生成一个[1:n + 1] 的数组 +- 我们遍历一次数组,对于每一个数组项,我们执行以下逻辑 +- 对于每一项,我们都假设其是断点。断点左侧的是 A,断点右侧的是 B。 +- 那么 A 就是 i - 1 个数, 那么 B 就是 n - i 个数 +- 我们递归,并将 A 和 B 的结果相乘即可。 + +> 其实我们发现,题目的答案只和 n 有关,和具体 n 个数的具体组成,只要是有序数组即可。 + +题目没有明确 n 的取值范围,我们试一下暴力递归。 + +代码(Python3): + +```python +class Solution: + def numTrees(self, n: int) -> int: + if n <= 1: + return 1 + res = 0 + for i in range(1, n + 1): + res += self.numTrees(i - 1) * self.numTrees(n - i) + return res +``` + +上面的代码会超时,并没有栈溢出,因此我们考虑使用 hashmap 来优化,代码见下方代码区。 + +## 关键点解析 + +- 分治法 +- 笛卡尔积 +- 记忆化递归 + +## 代码 + +- 语言支持:Python3 + +Python3 Code: + +```Python +class Solution: + visited = dict() + + def numTrees(self, n: int) -> int: + if n in self.visited: + return self.visited.get(n) + if n <= 1: + return 1 + res = 0 + for i in range(1, n + 1): + res += self.numTrees(i - 1) * self.numTrees(n - i) + self.visited[n] = res + return res +``` + +## 相关题目 + +- [95.unique-binary-search-trees-ii](./95.unique-binary-search-trees-ii.md) diff --git a/problems/98.validate-binary-search-tree.md b/problems/98.validate-binary-search-tree.md new file mode 100644 index 0000000..2a08c27 --- /dev/null +++ b/problems/98.validate-binary-search-tree.md @@ -0,0 +1,344 @@ +## 题目地址 +https://leetcode.com/problems/validate-binary-search-tree/description/ + +## 题目描述 +``` +Given a binary tree, determine if it is a valid binary search tree (BST). + +Assume a BST is defined as follows: + +The left subtree of a node contains only nodes with keys less than the node's key. +The right subtree of a node contains only nodes with keys greater than the node's key. +Both the left and right subtrees must also be binary search trees. + + +Example 1: + + 2 + / \ + 1 3 + +Input: [2,1,3] +Output: true +Example 2: + + 5 + / \ + 1 4 + / \ + 3 6 + +Input: [5,1,4,null,null,3,6] +Output: false +Explanation: The root node's value is 5 but its right child's value is 4. + + +``` + +## 思路 +### 中序遍历 +这道题是让你验证一棵树是否为二叉查找树(BST)。 由于中序遍历的性质`如果一个树遍历的结果是有序数组,那么他也是一个二叉查找树(BST)`, +我们只需要中序遍历,然后两两判断是否有逆序的元素对即可,如果有,则不是BST,否则即为一个BST。 + +### 定义法 +根据定义,一个结点若是在根的左子树上,那它应该小于根结点的值而大于左子树最小值;若是在根的右子树上,那它应该大于根结点的值而小于右子树最大值。也就是说,每一个结点必须落在某个取值范围: +1. 根结点的取值范围为(考虑某个结点为最大或最小整数的情况):(long_min, long_max) +2. 左子树的取值范围为:(current_min, root.value) +3. 右子树的取值范围为:(root.value, current_max) + +## 关键点解析 + +- 二叉树的基本操作(遍历) +- 中序遍历一个二叉查找树(BST)的结果是一个有序数组 +- 如果一个树遍历的结果是有序数组,那么他也是一个二叉查找树(BST) + +## 代码 + +### 中序遍历 + +* 语言支持:JS,C++, Java + +JavaScript Code: +```js +/* + * @lc app=leetcode id=98 lang=javascript + * + * [98] Validate Binary Search Tree + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {boolean} + */ +var isValidBST = function(root) { + if (root === null) return true; + if (root.left === null && root.right === null) return true; + const stack = [root]; + let cur = root; + let pre = null; + + function insertAllLefts(cur) { + while(cur && cur.left) { + const l = cur.left; + stack.push(l); + cur = l; + } + } + insertAllLefts(cur); + + while(cur = stack.pop()) { + if (pre && cur.val <= pre.val) return false; + const r = cur.right; + + if (r) { + stack.push(r); + insertAllLefts(r); + } + pre = cur; + } + + return true; +}; + +``` + +C++ Code: + +```c++ +// 递归 +class Solution { +public: + bool isValidBST(TreeNode* root) { + TreeNode* prev = nullptr; + return validateBstInorder(root, prev); + } + +private: + bool validateBstInorder(TreeNode* root, TreeNode*& prev) { + if (root == nullptr) return true; + if (!validateBstInorder(root->left, prev)) return false; + if (prev != nullptr && prev->val >= root->val) return false; + prev = root; + return validateBstInorder(root->right, prev); + } +}; + +// 迭代 +class Solution { +public: + bool isValidBST(TreeNode* root) { + auto s = vector(); + TreeNode* prev = nullptr; + while (root != nullptr || !s.empty()) { + while (root != nullptr) { + s.push_back(root); + root = root->left; + } + root = s.back(); + s.pop_back(); + if (prev != nullptr && prev->val >= root->val) return false; + prev = root; + root = root->right; + } + return true; + } +}; +``` + +Java Code: + +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +class Solution { + public boolean isValidBST(TreeNode root) { + Stack stack = new Stack<> (); + TreeNode previous = null; + + while (root != null || !stack.isEmpty()) { + while (root != null) { + stack.push(root); + root = root.left; + } + root = stack.pop(); + if (previous != null && root.val <= previous.val ) return false; + previous = root; + root = root.right; + } + return true; + } +} +``` + +### 定义法 + +* 语言支持:C++,Python3, Java, JS + +C++ Code: + +```C++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +// 递归 +class Solution { +public: + bool isValidBST(TreeNode* root) { + return helper(root, LONG_MIN, LONG_MAX); + } +private: + bool helper(const TreeNode* root, long min, long max) { + if (root == nullptr) return true; + if (root->val >= max || root->val <= min) return false; + return helper(root->left, min, root->val) && helper(root->right, root->val, max); + } +}; + +// 循环 +class Solution { +public: + bool isValidBST(TreeNode* root) { + if (root == nullptr) return true; + auto ranges = queue>(); + ranges.push(make_pair(LONG_MIN, LONG_MAX)); + auto nodes = queue(); + nodes.push(root); + while (!nodes.empty()) { + auto sz = nodes.size(); + for (auto i = 0; i < sz; ++i) { + auto range = ranges.front(); + ranges.pop(); + auto n = nodes.front(); + nodes.pop(); + if (n->val >= range.second || n->val <= range.first) { + return false; + } + if (n->left != nullptr) { + ranges.push(make_pair(range.first, n->val)); + nodes.push(n->left); + } + if (n->right != nullptr) { + ranges.push(make_pair(n->val, range.second)); + nodes.push(n->right); + } + } + } + return true; + } +}; +``` + +Python Code: + +```Python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def isValidBST(self, root: TreeNode, area: tuple=(-float('inf'), float('inf'))) -> bool: + """思路如上面的分析,用Python表达会非常直白 + :param root TreeNode 节点 + :param area tuple 取值区间 + """ + if root is None: + return True + + is_valid_left = root.left is None or\ + (root.left.val < root.val and area[0] < root.left.val < area[1]) + is_valid_right = root.right is None or\ + (root.right.val > root.val and area[0] < root.right.val < area[1]) + + # 左右节点都符合,说明本节点符合要求 + is_valid = is_valid_left and is_valid_right + + # 递归下去 + return is_valid\ + and self.isValidBST(root.left, (area[0], root.val))\ + and self.isValidBST(root.right, (root.val, area[1])) +``` + +Java Code: + +```java +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +class Solution { + public boolean isValidBST(TreeNode root) { + return helper(root, null, null); + } + + private boolean helper(TreeNode root, Integer lower, Integer higher) { + if (root == null) return true; + + if (lower != null && root.val <= lower) return false; + if (higher != null && root.val >= higher) return false; + + if (!helper(root.left, lower, root.val)) return false; + if (!helper(root.right, root.val, higher)) return false; + + return true; + } +} +``` + +JavaScript Code: + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {boolean} + */ +var isValidBST = function (root) { + if (!root) return true; + return valid(root); +}; + +function valid(root, min = -Infinity, max = Infinity) { + if (!root) return true; + const val = root.val; + if (val <= min) return false; + if (val >= max) return false; + return valid(root.left, min, val) && valid(root.right, val, max) +} +``` + +## 相关题目 + +[230.kth-smallest-element-in-a-bst](./230.kth-smallest-element-in-a-bst.md) diff --git a/templates/daily/2019-06-03.md b/templates/daily/2019-06-03.md new file mode 100644 index 0000000..bca0821 --- /dev/null +++ b/templates/daily/2019-06-03.md @@ -0,0 +1,35 @@ +## 每日一题 - xxxxxx +> xxxx为题目名 +### 信息卡片 + +- 时间: 2019-06-03 +- 题目链接:xxxx +- tag:dp +> tag 可以多个,以逗号分隔,tag是一个枚举值,有 array dp math等,具体可以参考leetcode tag +- 题目描述: + +xxxxxxxx + + +### 参考答案 +>可以自己发挥一些东西,但是切记思路要讲明白, +必要的时候(尤其是比较复杂的逻辑)可以用图来说明 + +```js + + +``` +> 语言不做要求 +### 扩展 +> 存放扩展引申内容,可以不提供答案。 +### 其他优秀解答 +> 可以自己发挥一些东西,但是切记思路要讲明白, +必要的时候(尤其是比较复杂的逻辑)可以用图来说明。 + +> 这部分内容主要来自群成员的优秀思路和解答,也可以邀请他们本人来写。 + + + + +> 其他说明, 如果需要存图片的,统一放到assets/daily +如果需要存drawio的统一存到assets/drawio \ No newline at end of file diff --git a/templates/problems/1014.best-sightseeing-pair.md b/templates/problems/1014.best-sightseeing-pair.md new file mode 100644 index 0000000..9303967 --- /dev/null +++ b/templates/problems/1014.best-sightseeing-pair.md @@ -0,0 +1,73 @@ +# 如何提交题解 + +## 提交内容 + +提交内容需要包含至少: + +- 包含英文题解或者中文题解的一个 README。(当然可以两者都提供) +- 将的题解添加到项目主页的 README 中的对应位置,并添加 🆕 标志 + > 对应位置指的是按照难度和题目序号排列在合适位置 + +## 关于题解格式要求 + +至少包含以下几项: + +1. 题目地址(518. 零钱兑换 II) + +> (518. 零钱兑换 II) 替换成你自己的题目序号和名称 + +2. 题目描述 +3. 思路 +4. 关键点 +5. 代码 + +可选项: + +1. 扩展 +2. 相关题目 +3. 参考 + +读者可根据实际情况添加别的,但是一定不要有雷同出现。 比如增加“想法”这种标签,这个和思路是差不多的意思,为了一致性,请改为思路。 + +## 代码要求 + +对于代码部分,要写清楚自己的是什么代码。 比如 Python,Java,C++等。 并且代码块需要显式设置代码格式。 比如 : + +C++ Code: + +```c++ + + +``` + +Python Code: + +```python + + +``` + +JavaScript Code: + +```js +``` + +Java Code: + +```js +``` + +如何你提供了多种语言实现,那么需要在著名你使用了什么代码。 参考 https://github.com/azl397985856/leetcode/blob/master/problems/78.subsets.md + +## 注意点 + +- 对于中文题解,请提供力扣中国的链接和题目描述。 +- 对于英文题解,请提供力扣中国的链接和题目描述。 +- 对于复杂的题目需要提供图片描述 + +## Demo + +- [518.coin-change-2(中文题解)](https://github.com/azl397985856/leetcode/blob/master/problems/518.coin-change-2.md) +- [23.merge-k-sorted-lists(中文题解)](https://github.com/azl397985856/leetcode/blob/master/problems/23.merge-k-sorted-lists.md) + +- [25.reverse-nodes-in-k-groups-en(英文题解)](https://github.com/azl397985856/leetcode/blob/master/problems/25.reverse-nodes-in-k-groups-en.md) diff --git a/thanksGiving.md b/thanksGiving.md new file mode 100644 index 0000000..0704ca6 --- /dev/null +++ b/thanksGiving.md @@ -0,0 +1,122 @@ +## ThanksGaving + +就在今天,我的《leetcode题解》项目首次突破1wstar, 在这里我特地写下这篇文章来记录这个时刻,同时非常感谢大家的支持和陪伴。 + +![star-history](./assets/thanks-gaving/star-history.jpg) + +(star增长曲线图) + +前几天,去了一趟山城重庆,在那里遇到了最美的人和最漂亮的风景。 + +![chongqing-1](./assets/thanks-gaving/chongqing-1.jpeg) + +![chongqing-2](./assets/thanks-gaving/chongqing-2.jpeg) + +![chongqing-3](./assets/thanks-gaving/chongqing-3.jpeg) + +我是一个念旧的人,现在是节后的第一天,让我开启回忆模式: + +- 2017-05-30 项目成立,那是的它只是用来占位而已,目的就是让自己知道之后要做这件事。 + +![first commit](./assets/thanks-gaving/first-commit.jpg) + +(第一次提交) + +- 2017-12-14 项目也只有10个star,那是的项目其实就是几个孤零零的题目,事实上我也没有花精力去做。 + +- 2019-04 开始了井喷式的增长,我开始花时间去完成它,推广它 + +在朋友圈推广: + +![朋友圈宣传](./assets/thanks-gaving/朋友圈宣传.jpeg) + +(在朋友圈宣传) + +同时在掘金,sf以及知乎推广,但是收效不是很明显,那时候项目首次破百,这也是我第一个破百的项目。 + +- 之后我组建了微信和qq群,来让大家活跃起来,促进交流,戒指目前(2019-06-10)微信群总人数已经超过700, +里面有非常多的学生,留学生以及全球各地各大公司的员工。 + +![群聊-qq](./assets/thanks-gaving/群聊-qq.jpg) + +(qq群) + +![群聊-wechat](./assets/thanks-gaving/群聊-wechat.jpg) + +(微信群) + + +之后先后通过@每日时报, @阮一峰,@d2,@hello-github等的宣传,又迎来的一次高峰, +在那一段时间大概突破了1k。 + +![ruanyifeng](./assets/thanks-gaving/ruanyifeng.jpeg) + +(阮一峰的周报) + +![hello-github](./assets/thanks-gaving/hello-github.jpeg) + +(hello-github也收录了我和我的仓库) + +二次元的司徒正美老师虽然没有帮忙宣传,但是它的star也在某种程度上起到了宣传作用。 + +![司徒正美](./assets/thanks-gaving/司徒正美.jpeg) + +(司徒正美) + +并且之后这个项目在github trending活跃了一个月左右,甚至有一次冲上了日榜的总榜第一,并被“开发者头条”收入《GitHub Trending - All - Daily》。 + + +![日榜第一](./assets/thanks-gaving/日榜第一.jpeg) + +(日榜第一) + +![开发者头条](./assets/thanks-gaving/开发者头条.jpeg) + +(开发者头条的微博号) + + +截止到2019-06-10,项目star首次破万,幸运的是我刚好捕捉到了第9999个小可爱. + +![9999](./assets/thanks-gaving/9999.jpeg) + +(9999,一个很有意思的数字) + +- 现在这个项目不仅仅是自己做题,更多的是大家一起交流和成长。 + +现在,项目除了JS,也在逐步加入C++,python,多编程语言正在筹备中。 + +![多语言支持](./assets/thanks-gaving/多语言支持.jpg) + +(我们正在努力加入更多编程语言) + +另外,在大家的帮助下,我们也逐步走上了国际化,不仅仅有人来主动做翻译,还组建了电报群。 + +![英文主页](./assets/thanks-gaving/英文主页.jpg) + +(英文主页) + +![英语进展](./assets/thanks-gaving/英语进展.jpg) + +(英文翻译进展) + +也不知道什么时候,《量子论》竟然悄悄地在知乎帮我宣传。 + +![量子论](./assets/thanks-gaving/量子论.jpeg) + +(知乎 - 量子论) + +与此同时,我在知乎的最高赞竟然给了这条评论。 + +![知乎点赞](./assets/thanks-gaving/知乎点赞.jpeg) + +- 2019-06-04 首次在三个群里同步开通《每日一题》,大家也非常踊跃地帮忙整理题目,甚至出题给思路,非常感谢大家。 + +![daily-problems](./assets/thanks-gaving/daily-problems.jpg) + +非常感谢大家一直以来的陪伴和支持,我们一起努力,加油💪。 + +如果你还没有加入我们,看了这篇文章想加入,那么可以访问我的项目主页 [leetcode题解](https://github.com/azl397985856/leetcode) +我在这里等着你。 + +PS: 有没有熟悉重庆的小伙伴,想请教一点事情,愿意的话加我私聊吧,先谢谢啦! + diff --git a/thanksGiving2.md b/thanksGiving2.md new file mode 100644 index 0000000..35d222e --- /dev/null +++ b/thanksGiving2.md @@ -0,0 +1,98 @@ +假期这几天我买了《逆转裁判 123》合集,面对着这香喷喷的冷饭吃了半天。从 GBA 玩到 NDS,从 NDS 玩到 3DS, 现在 NS 虽然没有出新作有点遗憾。不过有了高清重制,也当是个回忆和收藏了 🎉🎉 + +目前打通了第一第二关,剩下的过一段时间再玩好啦 😁 +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7ppozhetbj30u01ppdjp.jpg) + +回到正题,就在今天,我的《leetcode 题解》项目成功突破 2w star, 并且现在 Github 搜索关键字"LeetCode"我的项目已经排名第一啦,这是继 1W star 之后的第二个巨大突破,非常感谢大家一路以来的支持和陪伴。 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7q10kli5lj310m0fm74y.jpg) + +最近在写一本关于 LeetCode 题解的书,有很多人表示想买,这无形之中给了我很大的压力,名字还没定,暂时给它取一个代号《攻克 LeetCode》。 + +## 新书《攻克 LeetCode》 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7ppnnvb7yj305i04b744.jpg) + +这里是[《攻克 LeetCode》的草稿目录](https://lucifer.ren/blog/2019/10/03/draft/),目前有 20 章的内容,本书要讲的内容就是 LeetCode 上反复出现的算法,经过我进一步提炼,抽取数百道题目在这里进行讲解,帮助大家理清整体思绪,从而高效率地刷题,做到事半功倍。我这里总结了 7 个常见的数据结构和 7 个常见的算法以及 5 个常见的算法思想。 + +7 个数据结构分别是: `数组,栈,队列,链表,二叉树,散列表,图` + +7 个算法分别是:`二分法,递归,回溯法,排序,双指针,滑动窗口,并查集` + +5 个算法思想分别是:`分治,贪心,深度优先遍历,广度优先遍历,动态规划` + +只有掌握了这些基础的数据结构和算法,更为复杂的算法才能得心应手,事半功倍。而 LeetCode 的题目虽然不断出新,但是最终用到的算法永远是那几个,很多题目都是穿着新的衣服的老面孔了。大家学好这些基础套路之后会更加明白这个道理。 + +后期可能会有大幅度修改,希望大家提出宝贵意见,以特别的方式参与到这本书的编写中来。 + +## 2W star 截图 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7r4ef4fwej30rm0ld75g.jpg) + +## Star 曲线 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7pom0rbu6j30p00f1glo.jpg) + +(star 增长曲线图) + +## 知乎引流 + +[上次](https://github.com/azl397985856/leetcode/blob/master/thanksGiving.md)主要讲了项目从开始建立到拥有 1W star 的经历,本次书接前文,继续讲一下后面的故事。 + +上回提到知乎上的“量子位”在帮我做宣传,引入了不小的流量。 我就想为什么不自己去拉流量呢?我自己以作者的角度去回答一些问题岂不是更好,更受欢迎么?于是我就开始在知乎上回答问题,很开心其中一个还获得了专业认可。 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7poxozmrmj30jw0gl0tp.jpg) + +事实上并没有我想的那么好,我回答了两个 LeetCode 话题的内容,虽然也有几百的点赞和感谢,但是这离我的目标还差很远。 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7pox4k95zj309q0b1mxa.jpg) + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7poz07qsrj30jk0h4q3y.jpg) + +但是转念一想,我知乎刚起步,也没什么粉丝,并且写答案的时间也就一个月左右,这样想就好多了。 我相信将来会有更多的人看到我的答案,然后加入进来。 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7pozi8bfrj308907w747.jpg) + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7ppnep24xj30to0pwaar.jpg) + +## 建立自己的博客 + +现在我发表的文章都是在各大平台。这有一个很大的问题就是各个平台很难满足你的需求,比如按照标签,按照日期进行归档。 甚至很多平台的阅读体验很差,比如没有导航功能,广告太多等。因此我觉得自己搭建一个博客还是很有必要的,这个渠道也为我吸引了少部分的流量,目前添加的主要内容大概有: + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7pp2i0818j308m07awej.jpg) + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7pp2vru72j30800hct8p.jpg) + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7pp34fjowj307z08za9y.jpg) + +总体上来说效果还是不错的,之后的文章会在博客首发,各个平台也会陆续更新,感兴趣的可以来个 RSS 订阅,订阅方式已经在[《每日一荐 - 九月刊》](https://lucifer.ren/blog/2019/09/30/daily-featured-2019-09/)里面介绍了。 + +## GithubDaily 的 推荐 + +GithubDaily 转载了量子位的文章也为我的仓库涨了至少几百的 star,非常感谢。GithubDaily 是一个拥有 3W 多读者的公众号,大家有兴趣的可以关注一波。 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7pp8r6isnj30kl0eq3yo.jpg) + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7pp9drlz0j30j90arq31.jpg) + +## 其他自媒体的推荐 + +一些其他自媒体也会帮忙推广我的项目 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7ppmlm7gyj30u00y10v5.jpg) + +## 口耳相传 + +我后来才知道竟然有海外华侨和一些华人社区都能看到我了。 + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7ppm3upr0j30ky0mmmxv.jpg) + +![](https://tva1.sinaimg.cn/large/006y8mN6ly1g7ppk0hlauj30ss1bmq5d.jpg) +(一亩三分地是一个集中讨论美国加拿大留学的论坛) + +另外通过朋友之间口耳相传的介绍也变得越来越多。 + +非常感谢大家一直以来的陪伴和支持,我们一起努力,加油 💪。 + +如果你还没有加入我们,看了这篇文章想加入,那么可以访问我的项目主页 [leetcode 题解](https://github.com/azl397985856/leetcode) +我在这里等着你。 diff --git a/thanksGiving3.md b/thanksGiving3.md new file mode 100644 index 0000000..a94ad57 --- /dev/null +++ b/thanksGiving3.md @@ -0,0 +1,65 @@ +差不多一年的时间,项目收获了第 3W 个 Star,平均差不多一天 100 左右的 star,非常感谢大家的关注和支持。 + +## 30k 截图 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvgnu8fk3j30se0kkdgo.jpg) + +## Star 曲线 + +Start 曲线上来看,有一点放缓。但是整体仍然是明显的上升趋势。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvg4gxal4j30rz0gugml.jpg) + +(star 增长曲线图) + +## 在力扣宣传 + +当力扣官方也开始做`每日一题`的时候,我的心情是复杂的。怎么官方也开始学我搞每日一题了么?为了信仰(蹭热度),我也毅然决然参加了每日一题活动,贡献了几十篇题解。 + +三月份是满勤奖,四月份有一次忘记了,缺卡一天。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvjk32fo9j30wl0q97cj.jpg) + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvjkp4y7ij307h05mt8p.jpg) + +## 新书即将上线 + +新书详情戳这里:[《或许是一本可以彻底改变你刷 LeetCode 效率的题解书》](https://lucifer.ren/blog/2020/04/07/leetcode-book.intro/),目前正在申请书号。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvgi7rhf2j30zg0l0wii.jpg) + +点名感谢各位作者,审阅,以及行政小姐姐。 + +## 视频题解 + +最近开始做视频题解了,目前更新了五个视频。和文字题解不同,视频题解可以承载的内容会更多。 https://space.bilibili.com/519510412 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvqb9gqbrj30qd0jrth0.jpg) + +我计划更新一些文字题解很难表述的内容,当然还会提供 PPT,如果你喜欢文字,直接看 PPT 即可。 + +视频题解部分,我会带你拆解 LeetCode 题目,识别常见问题,掌握常见套路。 + +注意:这不是教你解决某一道题的题解,而是掌握解题方法和思路的题解。 + +## 《力扣加加》上线啦 + +我们的官网`力扣加加`上线啦 💐💐💐💐💐,有专题讲解,每日一题,下载区和视频题解,后续会增加更多内容,还不赶紧收藏起来?地址:http://leetcode-solution.cn/ + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvghq7iygj30z60d040p.jpg) + +点名感谢@三天 @CYL @Josephjinn + +## 朋友的支持 + +很多朋友也在关注我的项目,非常开心。点名感谢 @被单-加加 @童欧巴。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvgnjszdwj30tu113ta5.jpg) + +## 交流群 + +交流群人数也有了很大的提升。 粉丝人数也扩充到了 7000+。交流群数目也增加到了 10 个。其中 QQ 群人数最多,有将近 1800 人。为了限制人数,我开启了收费模式,希望大家不要打我 😂。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1gdvgpycnp5j30tk156tah.jpg) + +非常感谢大家一直以来的陪伴和支持,Fighting 💪。 diff --git a/thinkings/DFS.md b/thinkings/DFS.md new file mode 100644 index 0000000..7809cb9 --- /dev/null +++ b/thinkings/DFS.md @@ -0,0 +1,20 @@ +# 深度优先遍历 + +深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。 + +深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。 + +因发明「深度优先搜索算法」,约翰 · 霍普克洛夫特与罗伯特 · 塔扬在 1986 年共同获得计算机领域的最高奖:图灵奖。 + +截止目前(2020-02-21),深度优先遍历在 LeetCode 中的题目是 129 道。在 LeetCode 中的题型绝对是超级大户了。而对于树的题目,我们基本上都可以使用 DFS 来解决,甚至我们可以基于 DFS 来做广度优先遍历。并不一定说 DFS 不可以做 BFS(广度优先遍历)的事情。而且由于 DFS 通常我们可以基于递归去做,因此算法会更简洁。 在对性能有很高邀请的场合,我建议你使用迭代,否则尽量使用递归,不仅写起来简单快速,还不容易出错。 + +另外深度优先遍历可以结合回溯专题来联系,建议将这两个专题放到一起来学习。 + +## 题目 + +这是我近期总结的几个 DFS 题目,后续会持续更新~ + +- [200. 岛屿数量](https://leetcode-cn.com/problems/number-of-islands/solution/mo-ban-ti-dao-yu-dfspython3-by-fe-lucifer-2/) 中等 + +- [695. 岛屿的最大面积](https://leetcode-cn.com/problems/max-area-of-island/solution/mo-ban-ti-dao-yu-dfspython3-by-fe-lucifer/) 中等 +- [979. 在二叉树中分配硬币](https://leetcode-cn.com/problems/distribute-coins-in-binary-tree/solution/tu-jie-dfspython3-by-fe-lucifer/) 中等 diff --git a/thinkings/GCD.md b/thinkings/GCD.md new file mode 100644 index 0000000..c99f97b --- /dev/null +++ b/thinkings/GCD.md @@ -0,0 +1,74 @@ +# 最大公约数 + +关于最大公约数有专门的研究。 而在 LeetCode 中虽然没有直接让你求解最大公约数的题目。但是却有一些间接需要你求解最大公约数的题目。 + +比如: + +- [914. 卡牌分组](https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards/solution/python3-zui-da-gong-yue-shu-914-qia-pai-fen-zu-by-/) +- [365. 水壶问题](https://leetcode-cn.com/problems/water-and-jug-problem/solution/bfszui-da-gong-yue-shu-by-fe-lucifer/) +- [1071. 字符串的最大公因子](https://leetcode-cn.com/problems/greatest-common-divisor-of-strings/solution/1071-zi-fu-chuan-de-zui-da-gong-yin-zi-zui-da-gong/) + +因此如何求解最大公约数就显得重要了。 + +## 定义法 + +```python +def GCD(a: int, b: int) -> int: + smaller = min(a, b) + while smaller: + if a % smaller == 0 and b % smaller == 0: + return smaller + smaller -= 1 +``` + +**复杂度分析** + +- 时间复杂度:最好的情况是执行一次循环体,最坏的情况是循环到 smaller 为 1,因此总的时间复杂度为 $O(N)$,其中 N 为 a 和 b 中较小的数。 +- 空间复杂度:$O(1)$。 + +## 辗转相除法 + +如果我们需要计算 a 和 b 的最大公约数,运用辗转相除法的话。首先,我们先计算出 a 除以 b 的余数 c,把问题转化成求出 b 和 c 的最大公约数;然后计算出 b 除以 c 的余数 d,把问题转化成求出 c 和 d 的最大公约数;再然后计算出 c 除以 d 的余数 e,把问题转化成求出 d 和 e 的最大公约数。..... 以此类推,逐渐把两个较大整数之间的运算转化为两个较小整数之间的运算,直到两个数可以整除为止。 + +```python +def GCD(a: int, b: int) -> int: + return a if b == 0 else GCD(b, a % b) +``` + +**复杂度分析** + +- 时间复杂度:$O(log(max(a, b)))$ +- 空间复杂度:空间复杂度取决于递归的深度,因此空间复杂度为 $O(log(max(a, b)))$ + +下面我们对上面的过程进行一个表形象地讲解,实际上这也是教材里面的讲解方式,我只是照搬过来,增加一下自己的理解罢了。我们来通过一个例子来讲解: + +假如我们有一块 1680 米 \* 640 米 的土地,我们希望讲起分成若干正方形的土地,且我们想让正方形土地的边长尽可能大,我们应该如何设计算法呢? + +实际上这正是一个最大公约数的应用场景,我们的目标就是求解 1680 和 640 的最大公约数。 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gdflglk6v2j30f104zgo0.jpg) + +将 1680 米 \* 640 米 的土地分割,相当于对将 400 米 \* 640 米 的土地进行分割。 为什么呢? 假如 400 米 \* 640 米分割的正方形边长为 x,那么有 640 % x == 0,那么肯定也满足剩下的两块 640 米 \* 640 米的。 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gdfliql4tvj30g805aq5k.jpg) + +我们不断进行上面的分割: + +![](https://tva1.sinaimg.cn/large/00831rSTly1gdflles5bsj307x08vgmv.jpg) + +直到边长为 80,没有必要进行下去了。 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gdfllz6hx4j30aa04uwer.jpg) + +辗转相除法如果 a 和 b 都很大的时候,a % b 性能会较低。在中国,《九章算术》中提到了一种类似辗转相减法的 [更相减损术](https://zh.wikisource.org/wiki/%E4%B9%9D%E7%AB%A0%E7%AE%97%E8%A1%93#-.7BA.7Czh-hans:.E5.8D.B7.3Bzh-hant:.E5.8D.B7.7D-.E7.AC.AC.E4.B8.80.E3.80.80.E6.96.B9.E7.94.B0.E4.BB.A5.E5.BE.A1.E7.94.B0.E7.96.87.E7.95.8C.E5.9F.9F "更相减损术")。它的原理是:`两个正整数 a 和 b(a>b),它们的最大公约数等于 a-b 的差值 c 和较小数 b 的最大公约数。`。 + +```python +def GCD(a: int, b: int) -> int: + if a == b: + return a + if a < b: + return GCD(b - a, a) + return GCD(a - b, b) +``` + +上面的代码会报栈溢出。原因在于如果 a 和 b 相差比较大的话,递归次数会明显增加,要比辗转相除法递归深度增加很多,最坏时间复杂度为 O(max(a, b)))。这个时候我们可以将`辗转相除法`和`更相减损术`做一个结合,从而在各种情况都可以获得较好的性能。 diff --git a/thinkings/basic-algorithm-en.md b/thinkings/basic-algorithm-en.md new file mode 100644 index 0000000..f9ddef8 --- /dev/null +++ b/thinkings/basic-algorithm-en.md @@ -0,0 +1,61 @@ +# Basic Algorithms + +## Analysis of Time Complexity + +## Algorithm Thoughts + +### Greedy Algorithms + +### Divide-and-Conquer + +### Dynamic Programming + +### Backtracking + +### Enumeration + +## Meta Algorithm + +### Sorting Algorithms + +Sorting algorithms are meta algorithm. Although they are not tested quit often, the core ideas are very useful. + +#### O(n^2) + +- Insertion Sort +- Selection Sort +- Shell's Sort (aka Diminishing Increment Sort) +- Bubble Sort + +#### O(nlogn) + +- Quick Sort +- Merge Sort +- Heap Sort + +#### O(n) + +- Bucket Sort +- Counting Sort +- Radix Sort + +### Searching Algorithm + +- BFPRT Algorithm +- Search Trees +- Search by Hashing + +## Algorithms for String + +- 朴素 +- KMP +- RK +- BM +- trie + +## Other + +- Number Theory +- Probability Theory +- Union-find Algorithm +- Matrix Algorithms \ No newline at end of file diff --git a/thinkings/basic-algorithm.md b/thinkings/basic-algorithm.md new file mode 100644 index 0000000..5c2f960 --- /dev/null +++ b/thinkings/basic-algorithm.md @@ -0,0 +1,59 @@ +# 基础算法 + +## 时间复杂度分析 + +## 算法思想 + +### 贪心算法 + +### 分治 + +### 动态规划 + +### 回溯法 + +### 枚举法 + +## 元算法 + +### 排序算法 +排序算法是一种元算法,直接考的很少,但是思想还是有用的。 +#### O(n^2) + +- 插入排序 +- 选择排序 +- 希尔排序 +- 冒泡排序 + +#### O(nlogn) + +- 快排 +- 归并排序 +- 堆排序 + +#### O(n) + +- 桶排序 +- 计数排序 +- 基数排序 + +### 查找算法 + +- 线性查找 +- 树查找 +- 散列表查找 + +## 字符串问题 + +- 朴素 +- KMP +- RK +- BM +- trie + +## 其他 + +- 数论 +- 概率论 +- 并查集 +- 矩阵运算 \ No newline at end of file diff --git a/thinkings/basic-data-structure-en.md b/thinkings/basic-data-structure-en.md new file mode 100644 index 0000000..3ce5f23 --- /dev/null +++ b/thinkings/basic-data-structure-en.md @@ -0,0 +1,334 @@ +# Basic data structure + +> WIP: the translation of `basic data structure` is on the way. + +This article is not going to intepret data structures, but help you to `review and understand` data structures and algorithms with real scenes. So, if you have a poor data structure foundation, you'd better to read some basic courses about data structures before reading this. + +This article is focused on frontend. We are expected to enhance your understanding to data structures from how data structures are implemented in frontend. + +## Linear structure + +Data structures can be divided into linear and non-linear structures logically. +The linear structure contains array, stack, linked list and so on. +The non-linear structure contains tree, graph and so on. + +> In fact, tree can be taken for a half-linear structure. + +It should be noted that, the linear and non-linear date structures do NOT mean that the data in those structure are stored in a linear or non-linear on the hard disk. It is just a logic partition. For example, binary tree can be stored in array. + +Generally speaking, the data structure which has `pre` and `next` is linear. +Such as Array and Linked List, actually the Linked List is a kind of `Single Tree`。 +### Array + +Array is the simplest data structure and is used in so many places. For example, array is perfectly appropriate to store a data list. And in fact, you can find array behind many other data structures. + +The stack and queue structures which will be mentioned later can be regarded as a kind of LIMITED array. You can find the detials in the corresponding sections. + +Now, let's have a look at some interesting examples. + +#### React Hooks + +`hooks` is essentially an array. + +![basic-data-structure-hooks.png](../assets/thinkings/basic-data-structure-hooks.png) + +So, why `hooks` uses array? Maybe we can find the answer from the other side. What if not array? + +```js + +function Form() { + // 1. Use the name state variable + const [name, setName] = useState('Mary'); + + // 2. Use an effect for persisting the form + useEffect(function persistForm() { + localStorage.setItem('formData', name); + }); + + // 3. Use the surname state variable + const [surname, setSurname] = useState('Poppins'); + + // 4. Use an effect for updating the title + useEffect(function updateTitle() { + document.title = name + ' ' + surname; + }); + + // ... +} +``` + +基于数组的方式,`Form`的hooks就是 [hook1, hook2, hook3, hook4], +我们可以得出这样的关系, hook1就是[name, setName] 这一对, +hook2就是persistForm这个。 + +如果不用数组实现,比如对象,Form的hooks就是 +```js +{ + 'key1': hook1, + 'key2': hook2, + 'key3': hook3, + 'key4': hook4, +} +``` +那么问题是key1,key2,key3,key4怎么取呢? + +关于React hooks 的本质研究,更多请查看[React hooks: not magic, just arrays](https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e) + +React 将`如何确保组件内部hooks保存的状态之间的对应关系`这个工作交给了 +开发人员去保证,即你必须保证HOOKS的顺序严格一致,具体可以看React 官网关于 Hooks Rule 部分。 + +### Queue + +Queue is a limited sequence. The elements in queue can only be removed from the head and only be added from the tail. + +> accoding to FIFO(fisrt-in-first-out) principle + +Queue is also a very common data structure with widespread application. Like message queue. + +> The queue in data structure is just like the queue in daily life. + +In IT area, a queue is a specific ADT(abstract data type) or set. The entities in the set are stored in a certain sequence. + +There are twe basic operations of queue: + +- Adding entity to the tail, which is called enqueue. +- Removing entity from the head, which is called dequeue. + +Explaining of FIFO: + +![basic-data-structure-queue](../assets/thinkings/basic-data-structure-queue.svg) + +(picture source: https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/queue/README.zh-CN.md) + +There is a problem, Head of Line Block (HOL), in HTTP/1.1. What is that? And how HTTP/2 solves the problem? + +In fact, the HOL are not only appearing in HTTP/1.1, but also in switcher. The key to this problem is queue structure. + +For the same TCP connection, all HTTP/1.0 requests will be add into a queue. Which means, the next request can be sent until the previous respond has been received. This block happens at the client side mostly. + +Just like waiting the traffic lights, if you are on the left-turn or right-turning lane, you cannot move even if the straight lane is good to go when the left/right turning light is still red. + +![basic-data-structure-queue-1](../assets/thinkings/basic-data-structure-queue-1.png) + +`HTTP/1.0` and `HTTP/1.1`: +Accoding to `HTTP/1.0` protocal, one TCP connect will be established for each request and be terminated immediately after receiving the corresponding response. And the next HTTP request cannot be sent until the response of previous request has been received. +According to `HTTP/1.1`, each connection is persistent connection by default. For the same TCP connection, it is allowed to send multiple `HTTP/1.1` request at the same time. In other words, it is unnecessary to send the next request after receiving the response of the previous one. This is the solution to the HOL bloking of `HTTP/1.0`. And, this is called `pipeline` in `HTTP/1.1`. +However, according to `HTTP/1.1`, all the responses are reqired to be sent back to client or brower in the sequence of that being received. In other words, one request received in front should be responded in front. The HOL blocking will happend when one request in front takes a long processing time. All later request have to wait for it. So, the HOL blocking of `HTTP/1.1` happends at the server side. + +The process can be represented as follow: + +![basic-data-structure-queue-2](../assets/thinkings/basic-data-structure-queue-2.png) + +### Stack + +Stack is a kind of limited sequence. It only supports to add or remove element at the **top** of stack. + +In IT area, a stack is an ADT (abstract data type) for representing a set of elements. + +There are basic operations of stack: + +- Adding element at the top (tail), which called `push` +- Removing the element at the top (tail), which called `pop` + +The two operations can be summarized as LIFO (last-in-first-out) or FILO (first-in-last-out) + +Besides, there is usually an operation called `peek` which is used to retrieve the first element of the stack or the element present at the top of the stack. Compared with `pop`, the `peek` operation won't remove the retrieved element from the stack. + +> Stack can be regarded as a pile of books or dishes. + +Explaining of `push` and `pop` operations: + +![basic-data-structure-stack](../assets/thinkings/basic-data-structure-stack.png) + +(Picture from: https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/stack/README.zh-CN.md) + +Stack has been used in many places and areas. For example, in browser, the Execution Stack is a basic stack structure. +So, the recursion and loop+stack are essentially the same thing. + +For example: + +```js +function bar() { + const a = 1 + const b = 2; + console.log(a, b) +} +function foo() { + const a = 1; + bar(); +} + +foo(); + + +``` + +It may look like this inside the program during executing: + +![basic-data-structure-call-stack](../assets/thinkings/basic-data-structure-call-stack.png) + +> The figure above does not contains the other parts of the execution context, like `this` and `scope` which are the key to closure. Here is not going to talk about the closure but to explain the stack structure. +> Some statements in community like *the `scope` of execution context is the variables which declared by the super class in execution stack* which are completely wrong. JS uses Lexical Scoping. And `scope` is the parent object of function when it is defined. There is nothing to do with the execution. + +The common use of stack including Base Conversion, bracket matching, stack shuffling, Infix Expression and Postfix Expression, etc. + +> There is a correspongding relationship between legal stack shuffling operations and legal bracket matching expressions. +> In another word, the number of conditions of Stack Shuffling with `n` elements equals the number of conditions of legal expressions of `n` pairs of brackets. + +### Linked List + +Linked List is the most basic data structure. So, it is quit important to make yourself master of understanding and using Linked List. + +![basic-data-structure-link-list](../assets/thinkings/basic-data-structure-link-list.svg) + +(Picture from: https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/linked-list/traversal) + +#### React Fiber + +Many people know that `fiber` is implemented on Linked List. But not many of them know the reason. So, let's have a look at the relationship between `fiber` and Linked list. + +The appearance of `fiber` solves the problem that `react` must +fiber 出现的目的其实是为了解决 react 在执行的时候是无法停下来的,需要一口气执行完的问题的。 + +![fiber-intro](../assets/thinkings/basic-data-structure-fiber-intro.png) + +图片来自 Lin Clark 在 ReactConf 2017 分享 + +上面已经指出了引入 fiber 之前的问题,就是 react 会阻止优先级高的代码(比如用户输入)执行。因此 fiber +打算自己自建一个`虚拟执行栈`来解决这个问题,这个虚拟执行栈的实现是链表。 + +Fiber 的基本原理是将协调过程分成小块,一次执行一块,然乎将运算结果保存起来,并判断是否有时间(react 自己实现了一个类似 requestIdleCallback 的功能)继续执行下一块。 +如果有时间,则继续。 否则跳出,让浏览器主线程歇一会,执行别的优先级高的代码。 + +当协调过程完成(所有的小块都运算完毕), 那么就会进入提交阶段, 真正的进行副作用(side effect)操作,比如更新DOM,这个过程是没有办法取消的,原因就是这部分有副作用。 + +问题的关键就是将协调的过程划分为一块块的,最后还可以合并到一起,有点像Map/Reduce。 + +React 必须重新实现遍历树的算法,从依赖于`内置堆栈的同步递归模型`,变为`具有链表和指针的异步模型`。 + +> Andrew 是这么说的: 如果你只依赖于[内置]调用堆栈,它将继续工作直到堆栈为空。。。 + +如果我们可以随意中断调用堆栈并手动操作堆栈帧,那不是很好吗? +这就是 React Fiber 的目的。 `Fiber 是堆栈的重新实现,专门用于 React 组件`。 你可以将单个 Fiber 视为一个`虚拟堆栈帧`。 + +react fiber 大概是这样的: + +```js +let fiber = { + tag: HOST_COMPONENT, + type: "div", + return: parentFiber, + children: childFiber, + sibling: childFiber, + alternate: currentFiber, + stateNode: document.createElement("div"), + props: { children: [], className: "foo"}, + partialState: null, + effectTag: PLACEMENT, + effects: [] +}; + +``` + +从这里可以看出fiber本质上是个对象,使用parent,child,sibling属性去构建fiber树来表示组件的结构树, +return, children, sibling也都是一个fiber,因此fiber看起来就是一个链表。 + +> 细心的朋友可能已经发现了, alternate也是一个fiber, 那么它是用来做什么的呢? +它其实原理有点像git, 可以用来执行git revert ,git commit等操作,这部分挺有意思,我会在我的《从零开发git》中讲解 + +想要了解更多的朋友可以看[这个文章](https://github.com/dawn-plex/translate/blob/master/articles/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-to-walk-the-components-tree.md) + +如果可以翻墙, 可以看[英文原文](https://medium.com/react-in-depth/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-67f1014d0eb7) + +[这篇文章](https://engineering.hexacta.com/didact-fiber-incremental-reconciliation-b2fe028dcaec)也是早期讲述fiber架构的优秀文章 + +我目前也在写关于《从零开发react系列教程》中关于fiber架构的部分,如果你对具体实现感兴趣,欢迎关注。 + +## Non-linear Structure + +The reason that we need non-linear structures is satisfying both of static operations and dynamic operations. + +### Tree + +The Tree structure is also used widely. From file system to the Internet, the organizational structure of many of them can be represented as tree structure. +The DOM (document object model) in frontend is also a tree structure. And `HTML` is a implementation of DSL (domain specific language) to describe this tree structure. + +In fact, Tree is one kind of graph. It is an acyclic connected graph, a maximal acyclic graph and a minimal connected graph. + +From another prespective, Tree is a recursive data structure. [Left-Child Right-Sibling Representation of Tree](https://www.geeksforgeeks.org/left-child-right-sibling-representation-tree/) can be used to help to understand the structure of Tree. + +The basic operations of Tree including preoder, inorder, postoder and hierarchical traversals. +It is very easy to distinguish preorder, inorder and postorder traversals: + +- the preorder, inorder and postorder refer to the position of root during traversal. +- the two children nodes are always traversed from left to right. +- preorder: `root` -> `left child` -> `right child` (recursive). +- inorder: `left child` -> `root` -> `right child` (recursive). +- postorder: `left child` -> `right child` -> `root` (recursive) + +Because Tree is a recursive data structure, it is very easy to complete tree traversal using recursion. +Basically, the algorithms of Tree are all based on the tree traversal. But the performance of recursion is always a problem. +So, it may be helpful with understanding and using *imperative iteration* traversal algorithms. + +Stack can be used to implement the iterative traversal with using less code. + +> If stack is used, make sure that the left and right children are pushed into stack in correct sequence. + +Important properties of Tree: + +- If a tree has `n` vertex, then it has `n-1` edges. +- There is only one path between any node and the root node. The length of this path is called the depth of the node. + +### Binary Tree + +Binary tree is the tree that the degree of each node is not more than 2. It is a special subset of tree. +It is interesting that the binary tree which is a kind of limited tree can be used to represent and implemented all tree structures. +The principle behind Binary Tree is the `Left-Child Right-Sibling Representation of Tree`. + +> Binary Tree is a paticular case of multiple-way tree. But when Binary Tree has root and is ordered, it can be used to describe the latter. +> +> In fact, just rotating the tree 45 degrees, you can get a tree represented by `Left-Child Right-Sibling` + +Related algorithms: + +- [94.binary-tree-inorder-traversal](../problems/94.binary-tree-inorder-traversal.md) +- [102.binary-tree-level-order-traversal](../problems/102.binary-tree-level-order-traversal.md) +- [103.binary-tree-zigzag-level-order-traversal](../problems/103.binary-tree-zigzag-level-order-traversal.md) +- [144.binary-tree-preorder-traversal](../problems/144.binary-tree-preorder-traversal.md) +- [145.binary-tree-postorder-traversal](../problems/145.binary-tree-postorder-traversal.md) +- [199.binary-tree-right-side-view](../problems/199.binary-tree-right-side-view.md) + +Related concepts: + +- Proper Binary Tree (all node degrees can only be even, that is 0 or 2) + +BTW, you can find more details and algorithms in the charpter [binary tree traversal](./binary-tree-traversal.md) + +#### Heap + +Heap is a kind of priority queue which is built in many data structure. But unfortunately, JS does not have a native implementation of this data structure. However, it won't be a problem for understanding and using this structure. + +Note that: heap is not the only implementation of `priority queue`, there're a lot of more complex +implementations + +Related algorithm: + +- [295.find-median-from-data-stream](../problems/295.find-median-from-data-stream.md) + +#### Binary Search Tree + +### Balanced Tree + +database engine + +#### AVL Tree + +#### Red-Black Tree + +### Trie(Prefix Tree) + +Related algorithm: + +- [208.implement-trie-prefix-tree](../problems/208.implement-trie-prefix-tree.md) + +### Graph diff --git a/thinkings/basic-data-structure.md b/thinkings/basic-data-structure.md new file mode 100644 index 0000000..402774b --- /dev/null +++ b/thinkings/basic-data-structure.md @@ -0,0 +1,459 @@ +# 基础的数据结构 + +这篇文章不是讲解数据结构的文章,而是结合现实的场景帮助大家`理解和复习`数据结构与算法, +如果你的数据结构基础很差,建议先去看一些基础教程,再转过来看。 + +本篇文章的定位是侧重于前端的,通过学习前端中实际场景的数据结构,从而加深大家对数据结构的理解和认识。 + +## 线性结构 + +数据结构我们可以从逻辑上分为线性结构和非线性结构。线性结构有 +数组,栈,链表等, 非线性结构有树,图等。 + +> 其实我们可以称树为一种半线性结构。 + +需要注意的是,线性和非线性不代表存储结构是线性的还是非线性的,这两者没有任何关系,它只是一种逻辑上的划分。 +比如我们可以用数组去存储二叉树。 + +一般而言,有前驱和后继的就是线性数据结构。比如数组和链表。其实一叉树就是链表。 + +### 数组 + +数组是最简单的数据结构了,很多地方都用到它。 比如有一个数据列表等,用它是再合适不过了。 +其实后面的数据结构很多都有数组的影子。 + +我们之后要讲的栈和队列其实都可以看成是一种`受限`的数组, 怎么个受限法呢?我们后面讨论。 + +我们来讲几个有趣的例子来加深大家对数组这种数据结构的理解。 + +#### React Hooks + +Hooks 的本质就是一个数组, 伪代码: + +![basic-data-structure-hooks.png](../assets/thinkings/basic-data-structure-hooks.png) + +那么为什么 hooks 要用数组? 我们可以换个角度来解释,如果不用数组会怎么样? + +```js +function Form() { + // 1. Use the name state variable + const [name, setName] = useState("Mary"); + + // 2. Use an effect for persisting the form + useEffect(function persistForm() { + localStorage.setItem("formData", name); + }); + + // 3. Use the surname state variable + const [surname, setSurname] = useState("Poppins"); + + // 4. Use an effect for updating the title + useEffect(function updateTitle() { + document.title = name + " " + surname; + }); + + // ... +} +``` + +基于数组的方式,Form 的 hooks 就是 [hook1, hook2, hook3, hook4], +我们可以得出这样的关系, hook1 就是[name, setName] 这一对, +hook2 就是 persistForm 这个。 + +如果不用数组实现,比如对象,Form 的 hooks 就是 + +```js +{ + 'key1': hook1, + 'key2': hook2, + 'key3': hook3, + 'key4': hook4, +} +``` + +那么问题是 key1,key2,key3,key4 怎么取呢? + +关于 React hooks 的本质研究,更多请查看[React hooks: not magic, just arrays](https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e) + +React 将`如何确保组件内部hooks保存的状态之间的对应关系`这个工作交给了 +开发人员去保证,即你必须保证 HOOKS 的顺序严格一致,具体可以看 React 官网关于 Hooks Rule 部分。 + +### 队列 + +队列是一种受限的序列,它只能够操作队尾和队首,并且只能只能在队尾添加元素,在队首删除元素。 + +队列作为一种最常见的数据结构同样有着非常广泛的应用, 比如消息队列 + +> "队列"这个名称,可类比为现实生活中排队(不插队的那种) + +在计算机科学中, 一个 队列(queue) 是一种特殊类型的抽象数据类型或集合。集合中的实体按顺序保存。 + +队列基本操作有两种: + +- 向队列的后端位置添加实体,称为入队 +- 从队列的前端位置移除实体,称为出队。 + +队列中元素先进先出 FIFO (first in, first out)的示意: + +![basic-data-structure-queue](../assets/thinkings/basic-data-structure-queue.svg) + +(图片来自 https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/queue/README.zh-CN.md) + +我们前端在做性能优化的时候,很多时候会提到的一点就是“HTTP 1.1 的队头阻塞问题”,具体来说 +就是 HTTP2 解决了 HTTP1.1 中的队头阻塞问题,但是为什么 HTTP1.1 有队头阻塞问题,HTTP2 究竟怎么解决的很多人都不清楚。 + +其实“队头阻塞”是一个专有名词,不仅仅这里有,交换器等其他都有这个问题,引起这个问题的根本原因是使用了`队列`这种数据结构。 + +对于同一个 tcp 连接,所有的 http1.0 请求放入队列中,只有前一个`请求的响应`收到了,然后才能发送下一个请求,这个阻塞主要发生在客户端。 + +这就好像我们在等红绿灯,即使旁边绿灯亮了,你的这个车道是红灯,你还是不能走,还是要等着。 + +![basic-data-structure-queue-1](../assets/thinkings/basic-data-structure-queue-1.png) + +`HTTP/1.0` 和 `HTTP/1.1`: +在`HTTP/1.0` 中每一次请求都需要建立一个 TCP 连接,请求结束后立即断开连接。 +在`HTTP/1.1` 中,每一个连接都默认是长连接(persistent connection)。对于同一个 tcp 连接,允许一次发送多个 http1.1 请求,也就是说,不必等前一个响应收到,就可以发送下一个请求。这样就解决了 http1.0 的客户端的队头阻塞,而这也就是`HTTP/1.1`中`管道(Pipeline)`的概念了。 +但是,`http1.1规定,服务器端的响应的发送要根据请求被接收的顺序排队`,也就是说,先接收到的请求的响应也要先发送。这样造成的问题是,如果最先收到的请求的处理时间长的话,响应生成也慢,就会阻塞已经生成了的响应的发送。也会造成队头阻塞。 +可见,http1.1 的队首阻塞发生在服务器端。 + +如果用图来表示的话,过程大概是: + +![basic-data-structure-queue-2](../assets/thinkings/basic-data-structure-queue-2.png) + +`HTTP/2` 和 `HTTP/1.1`: + +为了解决`HTTP/1.1`中的服务端队首阻塞,`HTTP/2`采用了`二进制分帧` 和 `多路复用` 等方法。 +`二进制分帧`中,帧是`HTTP/2`数据通信的最小单位。在`HTTP/1.1`数据包是文本格式,而`HTTP/2`的数据包是二进制格式的,也就是二进制帧。采用帧可以将请求和响应的数据分割得更小,且二进制协议可以更高效解析。`HTTP/2`中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。 +`多路复用`用以替代原来的序列和拥塞机制。在`HTTP/1.1`中,并发多个请求需要多个 TCP 链接,且单个域名有 6-8 个 TCP 链接请求限制。在`HTTP/2`中,同一域名下的所有通信在单个链接完成,仅占用一个 TCP 链接,且在这一个链接上可以并行请求和响应,互不干扰。 + +> [此网站](https://http2.akamai.com/demo)可以直观感受`HTTP/1.1`和`HTTP/2`的性能对比。 + +### 栈 + +栈也是一种受限的序列,它只能够操作栈顶,不管入栈还是出栈,都是在栈顶操作。 + +在计算机科学中, 一个 栈(stack) 是一种抽象数据类型,用作表示元素的集合,具有两种主要操作: + +push, 添加元素到栈的顶端(末尾); +pop, 移除栈最顶端(末尾)的元素. +以上两种操作可以简单概括为“后进先出(LIFO = last in, first out)”。 + +此外,应有一个 peek 操作用于访问栈当前顶端(末尾)的元素。(只返回不弹出) + +> "栈"这个名称,可类比于一组物体的堆叠(一摞书,一摞盘子之类的)。 + +栈的 push 和 pop 操作的示意: + +![basic-data-structure-stack](../assets/thinkings/basic-data-structure-stack.png) + +(图片来自 https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/stack/README.zh-CN.md) + +栈在很多地方都有着应用,比如大家熟悉的浏览器就有很多栈,其实浏览器的执行栈就是一个基本的栈结构,从数据结构上说,它就是一个栈。 +这也就解释了,我们用递归的解法和用循环+栈的解法本质上是差不多。 + +比如如下 JS 代码: + +```js +function bar() { + const a = 1; + const b = 2; + console.log(a, b); +} +function foo() { + const a = 1; + bar(); +} + +foo(); +``` + +真正执行的时候,内部大概是这样的: + +![basic-data-structure-call-stack](../assets/thinkings/basic-data-structure-call-stack.png) + +> 我画的图没有画出执行上下文中其他部分(this 和 scope 等), 这部分是闭包的关键,而我这里不是讲闭包的,是为了讲解栈的。 + +> 社区中有很多“执行上下文中的 scope 指的是执行栈中父级声明的变量”说法,这是完全错误的, JS 是词法作用域,scope 指的是函数定义时候的父级,和执行没关系 + +栈常见的应用有进制转换,括号匹配,栈混洗,中缀表达式(用的很少),后缀表达式(逆波兰表达式)等。 + +> 合法的栈混洗操作,其实和合法的括号匹配表达式之间存在着一一对应的关系, +> 也就是说 n 个元素的栈混洗有多少种,n 对括号的合法表达式就有多少种。感兴趣的可以查找相关资料 + +### 链表 + +链表是一种最基本数据结构,熟练掌握链表的结构和常见操作是基础中的基础。 + +![basic-data-structure-link-list](../assets/thinkings/basic-data-structure-link-list.svg) + +(图片来自: https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/linked-list/traversal) + +#### React Fiber + +很多人都说 fiber 是基于链表实现的,但是为什么要基于链表呢,可能很多人并没有答案,那么我觉得可以把这两个点(fiber 和链表)放到一起来讲下。 + +fiber 出现的目的其实是为了解决 react 在执行的时候是无法停下来的,需要一口气执行完的问题的。 + +![fiber-intro](../assets/thinkings/basic-data-structure-fiber-intro.png) + +图片来自 Lin Clark 在 ReactConf 2017 分享 + +上面已经指出了引入 fiber 之前的问题,就是 react 会阻止优先级高的代码(比如用户输入)执行。因此 fiber +打算自己自建一个`虚拟执行栈`来解决这个问题,这个虚拟执行栈的实现是链表。 + +Fiber 的基本原理是将协调过程分成小块,一次执行一块,然乎将运算结果保存起来,并判断是否有时间(react 自己实现了一个类似 requestIdleCallback 的功能)继续执行下一块。 +如果有时间,则继续。 否则跳出,让浏览器主线程歇一会,执行别的优先级高的代码。 + +当协调过程完成(所有的小块都运算完毕), 那么就会进入提交阶段, 真正的进行副作用(side effect)操作,比如更新 DOM,这个过程是没有办法取消的,原因就是这部分有副作用。 + +问题的关键就是将协调的过程划分为一块块的,最后还可以合并到一起,有点像 Map/Reduce。 + +React 必须重新实现遍历树的算法,从依赖于`内置堆栈的同步递归模型`,变为`具有链表和指针的异步模型`。 + +> Andrew 是这么说的: 如果你只依赖于[内置]调用堆栈,它将继续工作直到堆栈为空。。。 + +如果我们可以随意中断调用堆栈并手动操作堆栈帧,那不是很好吗? +这就是 React Fiber 的目的。 `Fiber 是堆栈的重新实现,专门用于 React 组件`。 你可以将单个 Fiber 视为一个`虚拟堆栈帧`。 + +react fiber 大概是这样的: + +```js +let fiber = { + tag: HOST_COMPONENT, + type: "div", + return: parentFiber, + children: childFiber, + sibling: childFiber, + alternate: currentFiber, + stateNode: document.createElement("div"), + props: { children: [], className: "foo" }, + partialState: null, + effectTag: PLACEMENT, + effects: [] +}; +``` + +从这里可以看出 fiber 本质上是个对象,使用 parent,child,sibling 属性去构建 fiber 树来表示组件的结构树, +return, children, sibling 也都是一个 fiber,因此 fiber 看起来就是一个链表。 + +> 细心的朋友可能已经发现了, alternate 也是一个 fiber, 那么它是用来做什么的呢? +> 它其实原理有点像 git, 可以用来执行 git revert ,git commit 等操作,这部分挺有意思,我会在我的《从零开发 git》中讲解 + +想要了解更多的朋友可以看[这个文章](https://github.com/dawn-plex/translate/blob/master/articles/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-to-walk-the-components-tree.md) + +如果可以翻墙, 可以看[英文原文](https://medium.com/react-in-depth/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-67f1014d0eb7) + +[这篇文章](https://engineering.hexacta.com/didact-fiber-incremental-reconciliation-b2fe028dcaec)也是早期讲述 fiber 架构的优秀文章 + +我目前也在写关于《从零开发 react 系列教程》中关于 fiber 架构的部分,如果你对具体实现感兴趣,欢迎关注。 + +## 非线性结构 + +那么有了线性结构,我们为什么还需要非线性结构呢? 答案是为了高效地兼顾静态操作和动态操作。大家可以对照各种数据结构的各种操作的复杂度来直观感受一下。 + +### 树 + +树的应用同样非常广泛,小到文件系统,大到因特网,组织架构等都可以表示为树结构,而在我们前端眼中比较熟悉的 DOM 树也是一种树结构,而 HTML 作为一种 DSL 去描述这种树结构的具体表现形式。如果你接触过 AST,那么 AST 也是一种树,XML 也是树结构。。。树的应用远比大多数人想象的要得多。 + +树其实是一种特殊的`图`,是一种无环连通图,是一种极大无环图,也是一种极小连通图。 + +从另一个角度看,树是一种递归的数据结构。而且树的不同表示方法,比如不常用的`长子 + 兄弟`法,对于 +你理解树这种数据结构有着很大用处, 说是一种对树的本质的更深刻的理解也不为过。 + +树的基本算法有前中后序遍历和层次遍历,有的同学对前中后这三个分别具体表现的访问顺序比较模糊,其实当初我也是一样的,后面我学到了一点,你只需要记住:`所谓的前中后指的是根节点的位置,其他位置按照先左后右排列即可`。比如前序遍历就是`根左右`, 中序就是`左根右`,后序就是`左右根`, 很简单吧? + +我刚才提到了树是一种递归的数据结构,因此树的遍历算法使用递归去完成非常简单,幸运的是树的算法基本上都要依赖于树的遍历。 但是递归在计算机中的性能一直都有问题,因此掌握不那么容易理解的"命令式地迭代"遍历算法在某些情况下是有用的。如果你使用迭代式方式去遍历的话,可以借助上面提到的`栈`来进行,可以极大减少代码量。 + +> 如果使用栈来简化运算,由于栈是 FILO 的,因此一定要注意左右子树的推入顺序。 + +树的重要性质: + +- 如果树有 n 个顶点,那么其就有 n - 1 条边,这说明了树的顶点数和边数是同阶的。 +- 任何一个节点到根节点存在`唯一`路径, 路径的长度为节点所处的深度 + +实际使用的树有可能会更复杂,比如使用在游戏中的碰撞检测可能会用到四叉树或者八叉树。以及 k 维的树结构 `k-d 树`等。 + +![](https://tva1.sinaimg.cn/large/006tNbRwgy1gajhqov8pjj306y06mweo.jpg) +(图片来自 https://zh.wikipedia.org/wiki/K-d%E6%A0%91) + +### 二叉树 + +二叉树是节点度数不超过二的树,是树的一种特殊子集,有趣的是二叉树这种被限制的树结构却能够表示和实现所有的树, +它背后的原理正是`长子 + 兄弟`法,用邓老师的话说就是`二叉树是多叉树的特例,但在有根且有序时,其描述能力却足以覆盖后者`。 + +> 实际上, 在你使用`长子 + 兄弟`法表示树的同时,进行 45 度角旋转即可。 + +一个典型的二叉树: + +标记为 7 的节点具有两个子节点, 标记为 2 和 6; 一个父节点,标记为 2,作为根节点, 在顶部,没有父节点。 + +![basic-tree](../assets/thinkings/basic-tree.svg) + +(图片来自 https://github.com/trekhleb/javascript-algorithms/blob/master/src/data-structures/tree/README.zh-CN.md) + +对于一般的树,我们通常会去遍历,这里又会有很多变种。 + +下面我列举一些二叉树遍历的相关算法: + +- [94.binary-tree-inorder-traversal](../problems/94.binary-tree-inorder-traversal.md) +- [102.binary-tree-level-order-traversal](../problems/102.binary-tree-level-order-traversal.md) +- [103.binary-tree-zigzag-level-order-traversal](../problems/103.binary-tree-zigzag-level-order-traversal.md) +- [144.binary-tree-preorder-traversal](../problems/144.binary-tree-preorder-traversal.md) +- [145.binary-tree-postorder-traversal](../problems/145.binary-tree-postorder-traversal.md) +- [199.binary-tree-right-side-view](../problems/199.binary-tree-right-side-view.md) + +相关概念: + +- 真二叉树 (所有节点的度数只能是偶数,即只能为 0 或者 2) + +另外我也专门开设了[二叉树的遍历](./binary-tree-traversal.md)章节, 具体细节和算法可以去那里查看。 + +#### 堆 + +堆其实是一种优先级队列,在很多语言都有对应的内置数据结构,很遗憾 javascript 没有这种原生的数据结构。 +不过这对我们理解和运用不会有影响。 + +堆的特点: + +- 在一个 最小堆(min heap) 中, 如果 P 是 C 的一个父级节点, 那么 P 的 key(或 value)应小于或等于 C 的对应值. + 正因为此,堆顶元素一定是最小的,我们会利用这个特点求最小值或者第 k 小的值。 + +![min-heap](../assets/thinkings/min-heap.png) + +- 在一个 最大堆(max heap) 中, P 的 key(或 value)大于 C 的对应值。 + +![max-heap](../assets/thinkings/max-heap.svg) + +需要注意的是优先队列不仅有堆一种,还有更复杂的,但是通常来说,我们会把两者做等价。 + +相关算法: + +- [295.find-median-from-data-stream](../problems/295.find-median-from-data-stream.md) + +#### 二叉查找树 + +二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。 + +二叉查找树具有下列性质的二叉树: + +- 若左子树不空,则左子树上所有节点的值均小于它的根节点的值; +- 若右子树不空,则右子树上所有节点的值均大于它的根节点的值; +- 左、右子树也分别为二叉排序树; +- 没有键值相等的节点。 + +对于一个二叉查找树,常规操作有插入,查找,删除,找父节点,求最大值,求最小值。 + +二叉查找树,之所以叫查找树就是因为其非常适合查找,举个例子, +如下一颗二叉查找树,我们想找节点值小于且最接近 58 的节点,搜索的流程如图所示: + +![bst](../assets/thinkings/bst.png) +(图片来自 https://www.geeksforgeeks.org/floor-in-binary-search-tree-bst/) + +另外我们二叉查找树有一个性质是: `其中序遍历的结果是一个有序数组`。 +有时候我们可以利用到这个性质。 + +相关题目: + +- [98.validate-binary-search-tree](../problems/98.validate-binary-search-tree.md) + +### 二叉平衡树 + +平衡树是计算机科学中的一类数据结构,为改进的二叉查找树。一般的二叉查找树的查询复杂度取决于目标结点到树根的距离(即深度),因此当结点的深度普遍较大时,查询的均摊复杂度会上升。为了实现更高效的查询,产生了平衡树。 + +在这里,平衡指所有叶子的深度趋于平衡,更广义的是指在树上所有可能查找的均摊复杂度偏低。 + +一些数据库引擎内部就是用的这种数据结构,其目标也是将查询的操作降低到 logn(树的深度),可以简单理解为`树在数据结构层面构造了二分查找算法`。 + +基本操作: + +- 旋转 + +- 插入 + +- 删除 + +- 查询前驱 + +- 查询后继 + +#### AVL + +是最早被发明的自平衡二叉查找树。在 AVL 树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(logn)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis,他们在 1962 年的论文 An algorithm for the organization of information 中公开了这一数据结构。 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子 1、0 或 -1 的节点被认为是平衡的。带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。 + +#### 红黑树 + +在 1972 年由鲁道夫·贝尔发明,被称为"对称二叉 B 树",它现代的名字源于 Leo J. Guibas 和 Robert Sedgewick 于 1978 年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在 O(logn) 时间内完成查找,插入和删除,这里的 n 是树中元素的数目 + +### 字典树(前缀树) + +又称 Trie 树,是一种树形结构。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 + +![](https://tva1.sinaimg.cn/large/006tNbRwly1gajj0g78e6j30al06qwfc.jpg) + +(图来自 https://baike.baidu.com/item/%E5%AD%97%E5%85%B8%E6%A0%91/9825209?fr=aladdin) +它有 3 个基本性质: + +- 根节点不包含字符,除根节点外每一个节点都只包含一个字符; +- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; +- 每个节点的所有子节点包含的字符都不相同。 + +#### immutable 与 字典树 + +`immutableJS`的底层就是`share + tree`. 这样看的话,其实和字典树是一致的。 + +相关算法: + +- [208.implement-trie-prefix-tree](../problems/208.implement-trie-prefix-tree.md) +- [211.add-and-search-word-data-structure-design](../problems/211.add-and-search-word-data-structure-design.md) +- [212.word-search-ii](../problems/212.word-search-ii.md) + +## 图 + +前面讲的数据结构都可以看成是图的特例。 前面提到了二叉树完全可以实现其他树结构, +其实有向图也完全可以实现无向图和混合图,因此有向图的研究一直是重点考察对象。 + +图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。 + +## 图的表示方法 + +- 邻接矩阵(常见) + +空间复杂度 O(n^2),n 为顶点个数。 + +优点: + +1. 直观,简单。 + +2. 适用于稠密图 + +3. 判断两个顶点是否连接,获取入度和出度以及更新度数,时间复杂度都是 O(1) + +- 关联矩阵 +- 邻接表 + +对于每个点,存储着一个链表,用来指向所有与该点直接相连的点 +对于有权图来说,链表中元素值对应着权重 + +例如在无向无权图中: + +![graph-1](../assets/thinkings/graph-1.png) +(图片来自 https://zhuanlan.zhihu.com/p/25498681) + +可以看出在无向图中,邻接矩阵关于对角线对称,而邻接链表总有两条对称的边 +而在有向无权图中: + +![graph-2](../assets/thinkings/graph-2.png) + +(图片来自 https://zhuanlan.zhihu.com/p/25498681) + +## 图的遍历 + +图的遍历就是要找出图中所有的点,一般有以下两种方法: + +1. 深度优先遍历:(Depth First Search, DFS) + +深度优先遍历图的方法是,从图中某顶点 v 出发, 不断访问邻居, 邻居的邻居直到访问完毕。 + +2. 广度优先搜索:(Breadth First Search, BFS) + +广度优先搜索,可以被形象地描述为 "浅尝辄止",它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。 diff --git a/thinkings/binary-tree-traversal-en.md b/thinkings/binary-tree-traversal-en.md new file mode 100644 index 0000000..686235e --- /dev/null +++ b/thinkings/binary-tree-traversal-en.md @@ -0,0 +1,115 @@ +# Binary Tree Traversal + +## Overview + +Binary tree is a fundamental data structure. Traversal is a fundamental algorithm. Implementing traversal on binary tree makes a classical solution to problems. Many problems can be solved by trversaling binary tree, directly or indirectly. + +> If you have a good understanding of binary tree, it is not hard for you to understand other trees more complicated. + +The traversal of binary tree is basically comprised of in-order, pre-order, post-order and level-order traversals. + +A tree is typically traversed in two ways: + +- BFS (Breadth First Search, or Level Order Search) +- DFS (Depth First Search) + - In-order (Left-Root-Right) + - Pre-order (Root-Left-Right) + - Post-order (Left-Right-Root) + +Some problems can be solved with BFS and DFS, such as [301](../problems\301.remove-invalid-parentheses.md) and [609](../problems\609.find-duplicate-file-in-system.md) + +DFS simplifies operations with stack. Meanwhile, a tree is a recursive data structure. It is important to grasp recursion and stack for understanding DFS. + +Diagrammatic graph of DFS: + +![binary-tree-traversal-dfs](../assets/thinkings/binary-tree-traversal-dfs.gif) + +(source: https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/depth-first-search) + +The key point of BFS is whether the travesals of each level are completed or not. An identifier bit can be used to represent that. + +Then, let's talk about the in-order, pre-oder and post-order traversals. + +## Pre-order + +example: [144.binary-tree-preorder-traversal](../problems/144.binary-tree-preorder-traversal.md) + +order of traversal: `root-left-right` + +Algorithm Preorder(tree): + +1. visit the root. + +2. traverse the left subtree, i.e., call Preorder(left-subtree) + +3. traverse the right subtree, i.e., call Preorder(right-subtree) + +Summary: + +- typically recursive data structure. +- typycally algorithm which simplifies operations by stack. + +Actually, at the macro level, it can be described as `visit all left-side chain in sequence from top to bottom, and then, visit all right-side chain from bottom to top` + +If we think from this point of view, the algorithm can be different. All nodes in the left-side chain can be visited recursively from top to bottom directly. And all nodes in the right-side chain can be visited with the help of stack. + +The whole process is like this: + +![binary-tree-traversal-preorder](../assets/thinkings/binary-tree-traversal-preorder.png) + +This idea is something like the `backtrack`. It is important, because this idea can help us to `unify the understanding of three traversal methods`. + +## In-order + +example: [94.binary-tree-inorder-traversal](../problems/94.binary-tree-inorder-traversal.md) + +Order of In-order traversal: `Left-Root-Right` + +Algorithm Inorder(tree): + +1. Traverse the left subtree, i.e., call Inorder(left-subtree) + +2. Visit the root + +3. Traverse the right subtree, i.e., call Inorder(right-subtree) + +It is worth noting that, the result of traversing a BST (Binary Search Tree) is an ordered array. With this property, some problems can be simplified, such as: [230.kth-smallest-element-in-a-bst](../problems/230.kth-smallest-element-in-a-bst.md) and [98.validate-binary-search-tree](../problems/98.validate-binary-search-tree.md) + +## Post-order + +example: [145.binary-tree-postorder-traversal](../problems/145.binary-tree-postorder-traversal.md) + +Order of Post-order: `Left-Right-Root` + +This one is a liitle difficult to understand. + +But there is a clever trick to post-order traversal which is recording the trversal status of current node. +If + +1. current node is leaf node or +2. both left and right subtrees have been traversed + +the node can be popped out the stack. + +For condition 1, a leaf node is the node with no children (both left and right children are null); +For condition 2, variables are required for recording the traversal status for each node. Due to the stack, only one variable is indispensable bacause this variable can be used. + +## Level-order + +The key point of level-order is recording the traversal status of each level. An identifier bit can be used to represent the status of current level. + +![binary-tree-traversal-bfs](../assets/thinkings/binary-tree-traversal-bfs.gif) + +(source: https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/breadth-first-search) + +Algorithm Level-order(tree): + +1. push root into a queue. And put an identifier node into the queue which is null here. + +2. pop out one node from the queue. + +3. If the popped out node is not null, which means the traversal of current level is not finished, push the left and right node into the queue orderly. + +4. If the popped out node is null, which means the traversal of current level is finished. Now if the queue is null, the traversal is done. If the queue is not null, push a null node into the queue. + +example: [102.binary-tree-level-order-traversal](../problems/102.binary-tree-level-order-traversal.md) \ No newline at end of file diff --git a/thinkings/binary-tree-traversal.en.md b/thinkings/binary-tree-traversal.en.md new file mode 100644 index 0000000..ecad2e1 --- /dev/null +++ b/thinkings/binary-tree-traversal.en.md @@ -0,0 +1,193 @@ +# Binary Tree Traversal + +## Overview + +Binary tree as a basic data structure and traversal as a fundamental algorithm, their combination leads to a lot of classic problems. This patern is often seen in many problems, either directly or indirectly. + +> If you have grasped the traversal of binary trees, other complicated trees will probably be easy for you. + +Following are the generally used ways for traversing trees. + +- Depth First Traversals (DFS): Inorder, Preorder, Postorder + +- Breadth First or Level Order Traversal (BFS) + +There are applications for both DFS and BFS. Check out leetcode problem No.301 and No.609. + +Stack can be used to simplify the process of DFS traversal. Besides, since tree is a recursive data structure, recursion and stack are two key points for DFS. + +Graph for DFS: + +![binary-tree-traversal-dfs](../assets/thinkings/binary-tree-traversal-dfs.gif) + +(from https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/depth-first-search) + +The key point of BFS is how to decide whether the traversal of each level is done. The answer is using a variable as a flag to represent the end of the traversal of current level. + +Let's dive into details. + +## Preorder Traversal + +related problem[144.binary-tree-preorder-traversal](../problems/144.binary-tree-preorder-traversal.md) + +The traversal order of preorder traversal is `root-left-right`. + +Algorithm Preorder + +1. Visit the root node and push it into a stack. + +2. Pop a node from the stack, and push its right and left child node into the stack respectively. + +3. Repeat step 2. + +Conclusion: This problem involves the clasic recursive data structure (i.e. a binary tree), and the algorithm above demonstrates how a simplified solution can be reached by using a stack. + +If you look at the bigger picture, you'll find that the process of traversal is as followed. `Visit the left subtrees repectively from top to bottom, and visit the right subtrees repectively from bottom to top`. If we are to implement it from this perspective, things will be somewhat different. For the `top to bottom` part we can simply use recursion, and for the `bottom to top` part we can turn to stack. + +The traversal will look something like this. + +![binary-tree-traversal-preorder](../assets/thinkings/binary-tree-traversal-preorder.png) + +This way of problem solving is a bit similar to `backtrack`, on which I have written a post. You can benefit a lot from it because it can be used to `solve all three DFS traversal problems` mentioned aboved. If you don't know this yet, make a memo on it. + +## Inorder Traversal + +related problem[94.binary-tree-inorder-traversal](../problems/94.binary-tree-inorder-traversal.md) + +The traversal order of inorder traversal is `left-root-right`. + +So the root node is not printed first. Things are getting a bit complicated here. + +Algorithm Inorder + +1. Visit the root and push it into a stack. + +2. If there is a left child node, push it into the stack. Repeat this process until a leaf node reached. + +> At this point the root node and all the left nodes are in the stack. + +3. Start popping nodes from the stack. If a node has a right child node, push the child node into the stack. Repeat step 2. + +It's worth pointing out that the inorder traversal of a binary search tree (BST) is a sorted array, which is helpful for coming up simplified solutions for some problems. e.g. [230.kth-smallest-element-in-a-bst](../problems/230.kth-smallest-element-in-a-bst.md) and [98.validate-binary-search-tree](../problems/98.validate-binary-search-tree.md) + +## Postorder Traversal + +related problem[145.binary-tree-postorder-traversal](../problems/145.binary-tree-postorder-traversal.md) + +The traversal order of postorder traversal is `left-right-root`. + +This one is a bit of a challange. It deserves the `hard` tag of leetcode. + +In this case, the root node is printed not as the first but the last one. A cunning way to do it is to: + +Record whether the current node has been visited. If 1) it's a leaf node or 2) both its left and right subtrees have been traversed, then it can be popped from the stack. + +As for `1) it's a leaf node`, you can easily tell whether a node is a leaf if both its left and right are `null`. + +As for `2) both its left and right subtrees have been traversed`, we only need a variable to record whether a node has been visited or not. In the worst case, we need to record the status for every single node and the space complexity is O(n). But if you come to think about it, as we are using a stack and start printing the result from the leaf nodes, it makes sense that we only record the status for the current node popping from the stack, reducing the space complexity to O(1). Please click the link above for more details. + +## Level Order Traversal + +The key point of level order traversal is how do we know whether the traversal of each level is done. The answer is that we use a variable as a flag representing the end of the traversal of the current level. + +![binary-tree-traversal-bfs](../assets/thinkings/binary-tree-traversal-bfs.gif) + +(from https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/breadth-first-search) + +Algorithm Level Order + +1. Visit the root node, put it in a FIFO queue, put in the queue a special flag (we are using `null` here). + +2. Dequeue a node. + +3. If the node equals `null`, it means that all nodes of the current level have been visited. If the queue is empty, we do nothing. Or else we put in another `null`. + +4. If the node is not `null`, meaning the traversal of current level has not finished yet, we enqueue its left subtree and right subtree repectively. + +related problem[102.binary-tree-level-order-traversal](../problems/102.binary-tree-level-order-traversal.md) + +## Bi-color marking + +We know that there is a tri-color marking in garbage collection algorithm, which works as described below. + +- The white color represents "not visited". + +- The gray color represents "not all child nodes visited". + +- The black color represents "all child nodes visited". + +Enlightened by tri-color marking, a bi-color marking method can be invented to solve all three traversal problems with one solution. + +The core idea is as followed. + +- Use a color to mark whether a node has been visited or not. Nodes yet to be visited are marked as white and visited nodes are marked as gray. + +- If we are visiting a white node, turn it into gray, and push it's right child node, itself, and it's left child node into the stack respectively. + +- If we are visiting a gray node, print it. + +Implementing inorder traversal with tri-color marking: + +```python +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + WHITE, GRAY = 0, 1 + res = [] + stack = [(WHITE, root)] + while stack: + color, node = stack.pop() + if node is None: continue + if color == WHITE: + stack.append((WHITE, node.right)) + stack.append((GRAY, node)) + stack.append((WHITE, node.left)) + else: + res.append(node.val) + return res +``` + +Implementation of preorder and postorder traversal algorithms can be easily done by changing the order of pushing the child nodes into the stack. + +## Morris Traversal + +We can also use a method called Morris traversal, which involves no recursion or stack, and the time complexity is O(1). + +```python +def MorrisTraversal(root): + curr = root + + while curr: + # If left child is null, print the + # current node data. And, update + # the current pointer to right child. + if curr.left is None: + print(curr.data, end= " ") + curr = curr.right + + else: + # Find the inorder predecessor + prev = curr.left + + while prev.right is not None and prev.right is not curr: + prev = prev.right + + # If the right child of inorder + # predecessor already points to + # the current node, update the + # current with it's right child + if prev.right is curr: + prev.right = None + curr = curr.right + + # else If right child doesn't point + # to the current node, then print this + # node's data and update the right child + # pointer with the current node and update + # the current with it's left child + else: + print (curr.data, end=" ") + prev.right = curr + curr = curr.left +``` + +Reference: [what-is-morris-traversal](https://www.educative.io/edpresso/what-is-morris-traversal) diff --git a/thinkings/binary-tree-traversal.md b/thinkings/binary-tree-traversal.md new file mode 100644 index 0000000..0d54b81 --- /dev/null +++ b/thinkings/binary-tree-traversal.md @@ -0,0 +1,184 @@ +# 二叉树的遍历算法 + +## 概述 + +二叉树作为一个基础的数据结构,遍历算法作为一个基础的算法,两者结合当然是经典的组合了。 +很多题目都会有 ta 的身影,有直接问二叉树的遍历的,有间接问的。 + +> 你如果掌握了二叉树的遍历,那么也许其他复杂的树对于你来说也并不遥远了 + +二叉数的遍历主要有前中后遍历和层次遍历。 前中后属于 DFS,层次遍历属于 BFS。 +DFS 和 BFS 都有着自己的应用,比如 leetcode 301 号问题和 609 号问题。 + +DFS 都可以使用栈来简化操作,并且其实树本身是一种递归的数据结构,因此递归和栈对于 DFS 来说是两个关键点。 + +DFS 图解: + +![binary-tree-traversal-dfs](../assets/thinkings/binary-tree-traversal-dfs.gif) + +(图片来自 https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/depth-first-search) + +BFS 的关键点在于如何记录每一层次是否遍历完成, 我们可以用一个标识位来表式当前层的结束。 + +下面我们依次讲解: + +## 前序遍历 + +相关问题[144.binary-tree-preorder-traversal](../problems/144.binary-tree-preorder-traversal.md) + +前序遍历的顺序是`根-左-右` + +思路是: + +1. 先将根结点入栈 + +2. 出栈一个元素,将右节点和左节点依次入栈 + +3. 重复 2 的步骤 + +总结: 典型的递归数据结构,典型的用栈来简化操作的算法。 + +其实从宏观上表现为:`自顶向下依次访问左侧链,然后自底向上依次访问右侧链`, +如果从这个角度出发去写的话,算法就不一样了。从上向下我们可以直接递归访问即可,从下向上我们只需要借助栈也可以轻易做到。 +整个过程大概是这样: + +![binary-tree-traversal-preorder](../assets/thinkings/binary-tree-traversal-preorder.png) + +这种思路解题有点像我总结过的一个解题思路`backtrack` - 回溯法。这种思路有一个好处就是 +可以`统一三种遍历的思路`. 这个很重要,如果不了解的朋友,希望能够记住这一点。 + +## 中序遍历 + +相关问题[94.binary-tree-inorder-traversal](../problems/94.binary-tree-inorder-traversal.md) + +中序遍历的顺序是 `左-根-右`,根节点不是先输出,这就有一点点复杂了。 + +1. 根节点入栈 + +2. 判断有没有左节点,如果有,则入栈,直到叶子节点 + +> 此时栈中保存的就是所有的左节点和根节点。 + +3. 出栈,判断有没有右节点,有则入栈,继续执行 2 + +值得注意的是,中序遍历一个二叉查找树(BST)的结果是一个有序数组,利用这个性质有些题目可以得到简化, +比如[230.kth-smallest-element-in-a-bst](../problems/230.kth-smallest-element-in-a-bst.md), +以及[98.validate-binary-search-tree](../problems/98.validate-binary-search-tree.md) + +## 后序遍历 + +相关问题[145.binary-tree-postorder-traversal](../problems/145.binary-tree-postorder-traversal.md) + +后序遍历的顺序是 `左-右-根` + +这个就有点难度了,要不也不会是 leetcode 困难的 难度啊。 + +其实这个也是属于根节点先不输出,并且根节点是最后输出。 这里可以采用一种讨巧的做法, +就是记录当前节点状态,如果 1. 当前节点是叶子节点或者 2.当前节点的左右子树都已经遍历过了,那么就可以出栈了。 + +对于 1. 当前节点是叶子节点,这个比较好判断,只要判断 left 和 rigt 是否同时为 null 就好。 + +对于 2. 当前节点的左右子树都已经遍历过了, 我们只需要用一个变量记录即可。最坏的情况,我们记录每一个节点的访问状况就好了,空间复杂度 O(n) +但是仔细想一下,我们使用了栈的结构,从叶子节点开始输出,我们记录一个当前出栈的元素就好了,空间复杂度 O(1), 具体请查看上方链接。 + +## 层次遍历 + +层次遍历的关键点在于如何记录每一层次是否遍历完成, 我们可以用一个标识位来表式当前层的结束。 + +![binary-tree-traversal-bfs](../assets/thinkings/binary-tree-traversal-bfs.gif) + +(图片来自 https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/breadth-first-search) + +具体做法: + +1. 根节点入队列, 并入队列一个特殊的标识位,此处是 null + +2. 出队列 + +3. 判断是不是 null, 如果是则代表本层已经结束。我们再次判断是否当前队列为空,如果不为空继续入队一个 null,否则说明遍历已经完成,我们什么都不不用做 + +4. 如果不为 null,说明这一层还没完,则将其左右子树依次入队列。 + +相关问题[102.binary-tree-level-order-traversal](../problems/102.binary-tree-level-order-traversal.md) + +## 双色标记法 + +我们直到垃圾回收算法中,有一种算法叫三色标记法。 即: + +- 用白色表示尚未访问 +- 灰色表示尚未完全访问子节点 +- 黑色表示子节点全部访问 + +那么我们可以模仿其思想,使用双色标记法来统一三种遍历。 + +其核心思想如下: + +- 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。 +- 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。 +- 如果遇到的节点为灰色,则将节点的值输出。 + +使用这种方法实现的中序遍历如下: + +```python +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + WHITE, GRAY = 0, 1 + res = [] + stack = [(WHITE, root)] + while stack: + color, node = stack.pop() + if node is None: continue + if color == WHITE: + stack.append((WHITE, node.right)) + stack.append((GRAY, node)) + stack.append((WHITE, node.left)) + else: + res.append(node.val) + return res +``` + +如要实现前序、后序遍历,只需要调整左右子节点的入栈顺序即可。 + +## Morris 遍历 + +我们可以使用一种叫做 Morris 遍历的方法,既不使用递归也不借助于栈。从而在$O(1)$时间完成这个过程。 + +```python +def MorrisTraversal(root): + curr = root + + while curr: + # If left child is null, print the + # current node data. And, update + # the current pointer to right child. + if curr.left is None: + print(curr.data, end= " ") + curr = curr.right + + else: + # Find the inorder predecessor + prev = curr.left + + while prev.right is not None and prev.right is not curr: + prev = prev.right + + # If the right child of inorder + # predecessor already points to + # the current node, update the + # current with it's right child + if prev.right is curr: + prev.right = None + curr = curr.right + + # else If right child doesn't point + # to the current node, then print this + # node's data and update the right child + # pointer with the current node and update + # the current with it's left child + else: + print (curr.data, end=" ") + prev.right = curr + curr = curr.left +``` + +参考: [what-is-morris-traversal](https://www.educative.io/edpresso/what-is-morris-traversal) diff --git a/thinkings/bit.md b/thinkings/bit.md new file mode 100644 index 0000000..14db86a --- /dev/null +++ b/thinkings/bit.md @@ -0,0 +1,197 @@ +# 位运算 + +我这里总结了几道位运算的题目分享给大家,分别是 136和137, 260 和 645, 总共加起来四道题。 四道题全部都是位运算的套路,如果你想练习位运算的话,不要错过哦~~ + +## 前菜 + +开始之前我们先了解下异或,后面会用到。 + +1. 异或的性质 + +两个数字异或的结果`a^b`是将 a 和 b 的二进制每一位进行运算,得出的数字。 运算的逻辑是果同一位的数字相同则为 0,不同则为 1 + +2. 异或的规律 + +- 任何数和本身异或则为`0` + +- 任何数和 0 异或是`本身` + +3. 异或运算满足交换律,即: + +`a ^ b ^ c = a ^ c ^ b` + + +OK,我们来看下这三道题吧。 + +## 136. 只出现一次的数字1 + +题目大意是除了一个数字出现一次,其他都出现了两次,让我们找到出现一次的数。我们执行一次全员异或即可。 + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + single_number = 0 + for num in nums: + single_number ^= num + return single_number +``` +***复杂度分析*** +- 时间复杂度:$O(N)$,其中N为数组长度。 +- 空间复杂度:$O(1)$ + + +## 137. 只出现一次的数字2 + +题目大意是除了一个数字出现一次,其他都出现了三次,让我们找到出现一次的数。 灵活运用位运算是本题的关键。 + +Python3: + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + res = 0 + for i in range(32): + cnt = 0 # 记录当前 bit 有多少个1 + bit = 1 << i # 记录当前要操作的 bit + for num in nums: + if num & bit != 0: + cnt += 1 + if cnt % 3 != 0: + # 不等于0说明唯一出现的数字在这个 bit 上是1 + res |= bit + + return res - 2 ** 32 if res > 2 ** 31 - 1 else res +``` + + +- 为什么Python最后需要对返回值进行判断? + +如果不这么做的话测试用例是[-2,-2,1,1,-3,1,-3,-3,-4,-2] 的时候,就会输出 4294967292。 其原因在于Python是动态类型语言,在这种情况下其会将符号位置的1看成了值,而不是当作符号“负数”。 这是不对的。 正确答案应该是 - 4,-4的二进制码是 1111...100,就变成 2^32-4=4294967292,解决办法就是 减去 2 ** 32 。 + +> 之所以这样不会有问题的原因还在于题目限定的数组范围不会超过 2 ** 32 + +JavaScript: + +```js +var singleNumber = function(nums) { + let res = 0; + for (let i = 0; i < 32; i++) { + let cnt = 0; + let bit = 1 << i; + for (let j = 0; j < nums.length; j++) { + if (nums[j] & bit) cnt++; + } + if (cnt % 3 != 0) res = res | bit; + } + return res; +}; +``` + +***复杂度分析*** +- 时间复杂度:$O(N)$,其中N为数组长度。 +- 空间复杂度:$O(1)$ + +## 645. 错误的集合 + +和上面的`137. 只出现一次的数字2`思路一样。这题没有限制空间复杂度,因此直接hashmap 存储一下没问题。 不多说了,我们来看一种空间复杂度$O(1)$的解法。 + +由于和`137. 只出现一次的数字2`思路基本一样,我直接复用了代码。具体思路是,将nums的所有索引提取出一个数组idx,那么由idx和nums组成的数组构成singleNumbers的输入,其输出是唯二不同的两个数。 + +但是我们不知道哪个是缺失的,哪个是重复的,因此我们需要重新进行一次遍历,判断出哪个是缺失的,哪个是重复的。 + + +```python +class Solution: + def singleNumbers(self, nums: List[int]) -> List[int]: + ret = 0 # 所有数字异或的结果 + a = 0 + b = 0 + for n in nums: + ret ^= n + # 找到第一位不是0的 + h = 1 + while(ret & h == 0): + h <<= 1 + for n in nums: + # 根据该位是否为0将其分为两组 + if (h & n == 0): + a ^= n + else: + b ^= n + + return [a, b] + + def findErrorNums(self, nums: List[int]) -> List[int]: + nums = [0] + nums + idx = [] + for i in range(len(nums)): + idx.append(i) + a, b = self.singleNumbers(nums + idx) + for num in nums: + if a == num: + return [a, b] + return [b, a] + +``` + +***复杂度分析*** +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(1)$ + + +## 260. 只出现一次的数字3 + +题目大意是除了两个数字出现一次,其他都出现了两次,让我们找到这个两个数。 + +我们进行一次全员异或操作,得到的结果就是那两个只出现一次的不同的数字的异或结果。 + +我们刚才讲了异或的规律中有一个`任何数和本身异或则为0`, 因此我们的思路是能不能将这两个不同的数字分成两组 A 和 B。 +分组需要满足两个条件. + +1. 两个独特的的数字分成不同组 + +2. 相同的数字分成相同组 + +这样每一组的数据进行异或即可得到那两个数字。 + +问题的关键点是我们怎么进行分组呢? + +由于异或的性质是,同一位相同则为 0,不同则为 1. 我们将所有数字异或的结果一定不是 0,也就是说至少有一位是 1. + +我们随便取一个, 分组的依据就来了, 就是你取的那一位是 0 分成 1 组,那一位是 1 的分成一组。 +这样肯定能保证`2. 相同的数字分成相同组`, 不同的数字会被分成不同组么。 很明显当然可以, 因此我们选择是 1,也就是 +说`两个独特的的数字`在那一位一定是不同的,因此两个独特元素一定会被分成不同组。 + + +```python +class Solution: + def singleNumbers(self, nums: List[int]) -> List[int]: + ret = 0 # 所有数字异或的结果 + a = 0 + b = 0 + for n in nums: + ret ^= n + # 找到第一位不是0的 + h = 1 + while(ret & h == 0): + h <<= 1 + for n in nums: + # 根据该位是否为0将其分为两组 + if (h & n == 0): + a ^= n + else: + b ^= n + + return [a, b] +``` + +***复杂度分析*** +- 时间复杂度:$O(N)$,其中N为数组长度。 +- 空间复杂度:$O(1)$ + + +更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经接近30K star啦。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 + +![](https://pic.leetcode-cn.com/89ef69abbf02a2957838499a96ce3fbb26830aae52e3ab90392e328c2670cddc-file_1581478989502) diff --git a/thinkings/bloom-filter-en.md b/thinkings/bloom-filter-en.md new file mode 100644 index 0000000..5d5ce20 --- /dev/null +++ b/thinkings/bloom-filter-en.md @@ -0,0 +1,65 @@ +# Bloom-filter + +## Scenes + +Assume that you have a website which has so many audiences, what will you do if you want to know whether an IP address of a visitor is the first time to access your server or not. + +### Can hashtable do this? + +Of course yes. +Obviously, a hashtable with storing all IP addresses can tell us whether we have known one IP already. But, imagine that, if there are more than 1 billion IP addresses have been recorded, at least `4 Byte * 1,000,000,000 = 4,000,000,000 Byte = 4 GB` RAM is required. If it is not IP, but URL, the RAM required will be much larger. + +### Bit + +Another solution is using 1 bit to represent the access status of 1 IP, accessed or not. +For the same 1 billion IP addresses, now only `1 * 1,000,000,000 = 128 MB` RAM is required. If it is URL, this method uses much less spaces. + +With this method, only two operations are needed: `set(ip)` and `has(IP)`. + +However, this method has two fatal weakness: + +1. If elements are not distributed uniformly, a lot of spaces will not be used which is inefficient in space. + + > A good Hash function can be used to overcome this weakness. + +2. If the elements are not integer (e.g. URL), `BitSet` is inapplicable. + + > one or more Hash functions can also solve this. + +### Bloom Filter + +A Bloom filter is a space-efficient probabilistic data structure that is used to test whether an element is a member of a set. + +Actually, Bloom Filter is the second method with multiple hash functions. + +Here are four interesting properties of Bloom filter: + +- Unlike a standard hash table, a Bloom filter of a fixed size can represent a set with an arbitrarily large number of elements. + +- Adding an element never fails. However, the false positive rate increases steadily as elements are added until all bits in the filter are set to 1, at which point all queries yield a positive result. + +- Bloom filters never generate false negative result, i.e., telling you that a username doesn’t exist when it actually exists. + +- Deleting elements from filter is not possible because, if we delete a single element by clearing bits at indices generated by k hash functions, it might cause deletion of few other elements. + +![bloom-filter-url](../assets/thinkings/bloom-filter-url.png) + +### Application of Bloom Filter + +1. Network crawler + +whether an URL has been crawled or not. + +2. `key-value` database + +Whether a Key exists in this database or not. + +Example: +Each region of HBase has a Bloom Filter, which can be used to find whether a key exists in this region or not quickly. + +3. Phishing websites detection + +Sometimes browsers may alert that a website you are accessing is a phishing website. +Browsers use Bloom Filter to find wheter URL of one website exists in the phishing website database. + +> Hope this Algorithm can help you have a better understanding to `TRADEOFF`. diff --git a/thinkings/bloom-filter.md b/thinkings/bloom-filter.md new file mode 100644 index 0000000..ea18f29 --- /dev/null +++ b/thinkings/bloom-filter.md @@ -0,0 +1,48 @@ +## 场景 +假设你现在要处理这样一个问题,你有一个网站并且拥有`很多`访客,每当有用户访问时,你想知道这个ip是不是第一次访问你的网站。 + +### hashtable 可以么 +一个显而易见的答案是将所有的ip用hashtable存起来,每次访问都去hashtable中取,然后判断即可。但是题目说了网站有`很多`访客, +假如有10亿个用户访问过,每个ip的长度是4 byte,那么你一共需要4 * 1000000000 = 4000000000Bytes = 4G , 如果是判断URL黑名单, +由于每个URL会更长,那么需要的空间可能会远远大于你的期望。 + +### bit +另一个稍微难想到的解法是bit, 我们知道bit有0和1两种状态,那么用来表示存在,不存在再合适不过了。 + +加入有10亿个ip,我们就可以用10亿个bit来存储,那么你一共需要 1 * 1000000000 = (4000000000 / 8) Bytes = 128M, 变为原来的1/32, +如果是存储URL这种更长的字符串,效率会更高。 + +基于这种想法,我们只需要两个操作,set(ip) 和 has(ip) + +这样做有两个非常致命的缺点: + +1. 当样本分布极度不均匀的时候,会造成很大空间上的浪费 + +> 我们可以通过散列函数来解决 + +2. 当元素不是整型(比如URL)的时候,BitSet就不适用了 + +> 我们还是可以使用散列函数来解决, 甚至可以多hash几次 + +### 布隆过滤器 + +布隆过滤器其实就是`bit + 多个散列函数`, 如果经过多次散列的值再bit上都为1,那么可能存在(可能有冲突)。 如果 +有一个不为1,那么一定不存在(一个值经过散列函数得到的值一定是唯一的),这也是布隆过滤器的一个重要特点。 + +![bloom-filter-url](../assets/thinkings/bloom-filter-url.png) + +### 布隆过滤器的应用 + +1. 网络爬虫 +判断某个URL是否已经被爬取过 + +2. K-V数据库 判断某个key是否存在 + +比如Hbase的每个Region中都包含一个BloomFilter,用于在查询时快速判断某个key在该region中是否存在。 + +3. 钓鱼网站识别 + +浏览器有时候会警告用户,访问的网站很可能是钓鱼网站,用的就是这种技术 + +> 从这个算法大家可以对 tradeoff(取舍) 有更入的理解。 + diff --git a/thinkings/design.md b/thinkings/design.md new file mode 100644 index 0000000..ec7b729 --- /dev/null +++ b/thinkings/design.md @@ -0,0 +1,25 @@ +# 设计题 + +系统设计是一个没有标准答案的open-end问题,所以关键在于对于特定问题的设计选择,俗称trade-off。这也是较能考察面试者知识水平的一种题型。 + +截止目前(2020-03-28),[设计题](https://leetcode-cn.com/tag/design/)在LeetCode一共58道题目。 + +其中: + +- 简单14道 +- 中等32道 +- 困难12道 + +这里精选6道题目进行详细讲解,旨在大家能够对系统设计题的答题技巧和套路有所掌握,喜欢的话别忘了点赞和关注哦。 + + +## 题目列表 + +这是我近期总结的几道设计题目,后续会持续更新~ + +- [0155.min-stack](../problems/155.min-stack.md) 简单 +- [0211.add-and-search-word-data-structure-design](../problems/211.add-and-search-word-data-structure-design.md) 中等 +- [0232.implement-queue-using-stacks](../problems/232.implement-queue-using-stacks.md) 简单 +- [0460.lfu-cache](../problems/460.lfu-cache.md) 困难 +- [895.maximum-frequency-stack](../problems/895.maximum-frequency-stack.md) 困难 +- [900.rle-iterator](../problems/900.rle-iterator.md) 中等 diff --git a/thinkings/dynamic-programming-en.md b/thinkings/dynamic-programming-en.md new file mode 100644 index 0000000..a81e19c --- /dev/null +++ b/thinkings/dynamic-programming-en.md @@ -0,0 +1,159 @@ +# Recursion and Dynamic Programming + +> WIP: the translation of `Recursive and Dynamic Programming` is on the way. + +Dynamic Programming (DP) can be interpreted as the recursion of table look-up. Then, what is recursion? + +## Recursion + +Definition: The process in which a function calls itself directly or indirectly is called recursion and the corresponding function is called as recursive function. + +In some algorithms, recursion can help to implement loop functions very easily. For example, the traverse of Binary Tree. Recursion is widely used in algorithms, including the increasingly popular Functional programming. + +> In pure functional programming, there is no loops, but only recursion. + +Now, let's going to talk about recursion. In layman's terms, A recursive solution solves a problem by solving a smaller instance of the same problem. It solves this new problem by solving an even smaller instance of the same problem. Eventually, the new problem will be so small that its solution will either be obvious or known. This solution will lead to the solution of the original problem. + +### Three Key Factors of Recursion + +1. Each recursive call should be on a smaller instance of the same problem, that is, a smaller subproblem. +2. The recursive calls must eventually reach a base case, which is solved without further recursion. + +There are several questions which can be solved by using recursion easily: + +- [sum by recursion](https://www.geeksforgeeks.org/sum-of-natural-numbers-using-recursion/) +- [Traverse Binary Tree](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) +- [climbing stairs](https://leetcode.com/problems/climbing-stairs/) +- [tower of Hanoi](https://www.geeksforgeeks.org/c-program-for-tower-of-hanoi/) + +## Dynamic Programming (DP) + +> If we say, recursion is a detivation from the solution to the problem which trying to shrinkthe problem and solve it. Then DP solves probems by starting from a small condition and extending it to the optimal substructure. + +The thinking of recursion is intuitive. And it is easy to be implemented. But sometimes, with drawing a recursion tree to help analyse, we can find that recursion may bring extra computation during shriking the scale of the problem. +We are going to use recursion to solve [279.perfect-squares](../problems/279.perfect-squares.md) with using a buffer to store the intermediate result for reducing some computation. In fact, this is also the key idea in DP. + +Here is an example of calculate the sum of all items in the given array. + +code: + +```js +function sum(nums) { + if (nums.length === 0) return 0; + if (nums.length === 1) return nums[0]; + + return nums[0] + sum(nums.slice(1)); +} +``` + +Let's review this problem intuitively with a recursion tree. + +![dynamic-programming-1](../assets/thinkings/dynamic-programming-1.png) + +This method works, but not quit good. Because there are certain costs in executing functions. Let's take JS as example. +For each time a function executed, it requires stack push operations, pre-processing and executing processes. So, recurse a function is easy to cause stack overflow. + +> In browser, the JS exgine has limit to the length of code execution stack. The stack overflow exeption happens when the length of execution stack exceeds the limit. + +Another example for recursion: + +You are climbing a stair case. It takes n steps to reach to the top. +Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? + +code: + +```js +function climbStairs(n) { + if (n === 1) return 1; + if (n === 2) return 2; + return climbStairs(n - 1) + climbStairs(n - 2); +} +``` + +This question is just like the `fibnacci` series. Let's have a look at the recursion tree of `fibnacci` question again. + +![dynamic-programming-2](../assets/thinkings/dynamic-programming-2.png) + +Some results are calculated repeatedly. Just like the red nodes showing. If a structure like `hashtable` is used to store the intermedia results, the reduplicative calculations can be avoided. +Similarly, DP also uses "table look-up" to solve the problem of reduplicative calculation. + +Now let's talk more about the DP. + +How to start from a small condition to achieve the optimal substructure. + +This is the solution to the previous question with using DP: + +```js +function climbStairs(n) { + if (n === 1) return 1; + if (n === 2) return 2; + + let a = 1; + let b = 2; + let temp; + + for (let i = 3; i <= n; i++) { + temp = a + b; + a = b; + b = temp; + } + + return temp; +} +``` + +Here is the process of "table look-up" in DP: + +![dynamic-programming-3](../assets/thinkings/dynamic-programming-3.png) + +> dotted box is the "table look-up" + +This question is the most simplest one in DP, bacause it only contains a single factor. If it comes down to multiple factors, the question will be much more complex, such as knapsack problem. + +For one factor, we only need a one-dimentional array at most. But for knapsack problem, we may need two-dimentional or even higher dimentional array. + +Sometimes, we don't even need a one-dimentional array, just like the climbing stairs question. Bacause, in this question, only the past two states are required. That's why two varibles are enough. But not all DP questions have the shortcut like this. + +### Two Key Factors in DP + +1. state transfer function +2. critical condition + +In the previous question: + +``` +f(1) and f(2) are the critical conditions +f(n) = f(n-1) + f(n-2) is the state transfer function +``` + +### Why it is necessary to draw a table for solving DP questions + +Drawing a table to solve DP questions is a effective and efficient way. + +Essentially, DP breaks a problem into similar subproblems and solve the problem by solving its all subproblems. + +It is similar to recursion. But DP can reduce the time and space compexity by a way like table look-up. + +Drawing a table can help to complete the state transfer function. Each cell in the table is a subproblem. The process of filling the table, is the way to solve all subproblems. After solving all subproblems, it is easy to find the solution to the original problem. + +First, we solve the simplest subproblem which can be worked out directly. Then, with state transfer function, the adjacent cells can be worked out. Finnally, the last cell, generally at the low right corner of the table, is the solution to the problem. + +For example, using DP to solve Backpack problem, it makes decision that which cell should be selected with considering the previous subproblems `A[i-1][j] A[i-1][w-wj]`. The question needs the max value. So, we can work out the value respectively for the two different condition and choose the larger one and update the new cell. + +### Related Questions + +- [0091.decode-ways](../problems/91.decode-ways.md) +- [0139.word-break](../problems/139.word-break.md) +- [0198.house-robber](../problems/0198.house-robber.md) +- [0309.best-time-to-buy-and-sell-stock-with-cooldown](../problems/309.best-time-to-buy-and-sell-stock-with-cooldown.md) +- [0322.coin-change](../problems/322.coin-change.md) +- [0416.partition-equal-subset-sum](../problems/416.partition-equal-subset-sum.md) +- [0518.coin-change-2](../problems/518.coin-change-2.md) + +> there are much more related questions not presented. + +## Summary + +There are two important algorithms in this article: recursion and dynamic programming. + +If recursion is a bottom-top process, then DP is a top-bottom process. diff --git a/thinkings/dynamic-programming.md b/thinkings/dynamic-programming.md new file mode 100644 index 0000000..aa88db8 --- /dev/null +++ b/thinkings/dynamic-programming.md @@ -0,0 +1,191 @@ +# 递归和动态规划 + +动态规划可以理解为是查表的递归。那么什么是递归? + +## 递归 + +定义: 递归算法是一种直接或者间接调用自身函数或者方法的算法。 + +算法中使用递归可以很简单地完成一些用循环实现的功能,比如二叉树的左中右序遍历。递归在算法中有非常广泛的使用, +包括现在日趋流行的函数式编程。 + +> 纯粹的函数式编程中没有循环,只有递归。 + +接下来我们来讲解一下递归。通俗来说,递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解 + +### 递归的三个要素 + +1. 一个问题的解可以分解为几个子问题的解 +2. 子问题的求解思路除了规模之外,没有任何区别 +3. 有递归终止条件 + +我这里列举了几道算法题目,这几道算法题目都可以用递归轻松写出来: + +- 递归实现 sum + +- 二叉树的遍历 + +- 走楼梯问题 + +- 汉诺塔问题 + +- 杨辉三角 + +### 练习递归 + +一个简单练习递归的方式是将你写的迭代全部改成递归形式。比如你写了一个程序,功能是“将一个字符串逆序输出”,那么使用迭代将其写出来会非常容易,那么你是否可以使用递归写出来呢?通过这样的练习,可以让你逐步适应使用递归来写程序。 + +### 递归中的重复计算 + +递归中存在这么多的重复计算,一种简单的方式就是记忆化递归。即一边递归一边使用“记录表”记录我们已经计算过的情况,这样就避免了重复计算。而动态规划中 DP 数组其实和“记录表”一样。 + +你可以尝试使记忆化更加通用和非侵入性,即应用记忆化技术而不改变原来的功能。 (提示:可以参考一种被称作 decorator 的设计模式)。 + +### 递归的时间复杂度分析 + +敬请期待我的新书。 + +## 动态规划 + +`如果说递归是从问题的结果倒推,直到问题的规模缩小到寻常。 那么动态规划就是从寻常入手, 逐步扩大规模到最优子结构。` 这句话需要一定的时间来消化, +如果不理解,可以过一段时间再来看。 + +递归的解决问题非常符合人的直觉,代码写起来比较简单。但是我们通过分析(可以尝试画一个递归树),可以看出递归在缩小问题规模的同时可能会 +重复计算。 [279.perfect-squares](../problems/279.perfect-squares.md) 中 我通过递归的方式来解决这个问题,同时内部维护了一个缓存 +来存储计算过的运算,那么我们可以减少很多运算。 这其实和动态规划有着异曲同工的地方。 + +我们结合求和问题来讲解一下,题目是给定一个数组,求出数组中所有项的和,要求使用递归实现。 + +代码: + +```js +function sum(nums) { + if (nums.length === 0) return 0; + if (nums.length === 1) return nums[0]; + + return nums[0] + sum(nums.slice(1)); +} +``` + +我们用递归树来直观地看一下。 + +![dynamic-programming-1](../assets/thinkings/dynamic-programming-1.png) + +这种做法本身没有问题,但是每次执行一个函数都有一定的开销,拿 JS 引擎执行 JS 来说, +每次函数执行都会进行入栈操作,并进行预处理和执行过程,所以对于内存来说是一个挑战。 +很容易造成爆栈。 + +> 浏览器中的 JS 引擎对于代码执行栈的长度是有限制的,超过会爆栈,抛出异常。 + +我们再举一个更加明显的例子,问题描述: + +一个人爬楼梯,每次只能爬 1 个或 2 个台阶,假设有 n 个台阶,那么这个人有多少种不同的爬楼梯方法? + +代码: + +```js +function climbStairs(n) { + if (n === 1) return 1; + if (n === 2) return 2; + return climbStairs(n - 1) + climbStairs(n - 2); +} +``` + +这道题和 fibnacci 数列一摸一样,我们继续用一个递归树来直观感受以下: + +![dynamic-programming-2](../assets/thinkings/dynamic-programming-2.png) + +可以看出这里面有很多重复计算,我们可以使用一个 hashtable 去缓存中间计算结果,从而省去不必要的计算。 +那么动态规划是怎么解决这个问题呢? 答案就是“查表”。 + +刚才我们说了`递归是从问题的结果倒推,直到问题的规模缩小到寻常。 动态规划是从寻常入手, 逐步扩大规模到最优子结构。` + +从刚才的两个例子,我想大家可能对前半句话有了一定的理解,我们接下来讲解下后半句。 + +如果爬楼梯的问题,使用动态规划,代码是这样的: + +```js +function climbStairs(n) { + if (n === 1) return 1; + if (n === 2) return 2; + + let a = 1; + let b = 2; + let temp; + + for (let i = 3; i <= n; i++) { + temp = a + b; + a = b; + b = temp; + } + + return temp; +} +``` + +动态规划的查表过程如果画成图,就是这样的: + +![dynamic-programming-3](../assets/thinkings/dynamic-programming-3.png) + +> 虚线代表的是查表过程 + +这道题目是动态规划中最简单的问题了,因为设计到单个因素的变化,如果涉及到多个因素,就比较复杂了,比如著名的背包问题,挖金矿问题等。 + +对于单个因素的,我们最多只需要一个一维数组即可,对于如背包问题我们需要二维数组等更高纬度。 + +> 爬楼梯我们并没有使用一维数组,而是借助两个变量来实现的,空间复杂度是 O(1). +> 之所以能这么做,是因为爬楼梯问题的状态转移方程只和前两个有关,因此只需要存储这两个即可。 动态规划问题有时候有很多这种讨巧的方式,但并不是所有的 + +### 动态规划的两个要素 + +1. 状态转移方程 + +2. 临界条件 + +在上面讲解的爬楼梯问题中 + +``` +f(1) 与 f(2) 就是【边界】 +f(n) = f(n-1) + f(n-2) 就是【状态转移公式】 + +``` + +### 动态规划为什么要画表格 + +动态规划问题要画表格,但是有的人不知道为什么要画,就觉得这个是必然的,必要要画表格才是动态规划。 + +其实动态规划本质上是将大问题转化为小问题,然后大问题的解是和小问题有关联的,换句话说大问题可以由小问题进行计算得到。 + +这一点是和递归一样的, 但是动态规划是一种类似查表的方法来缩短时间复杂度和空间复杂度。 + +画表格的目的就是去不断推导,完成状态转移, 表格中的每一个 cell 都是一个`小问题`, 我们填表的过程其实就是在解决问题的过程, +我们先解决规模为寻常的情况,然后根据这个结果逐步推导,通常情况下,表格的右下角是问题的最大的规模,也就是我们想要求解的规模。 + +比如我们用动态规划解决背包问题, 其实就是在不断根据之前的小问题`A[i - 1][j] A[i -1][w - wj]`来询问: + +1. 我是应该选择它 +2. 还是不选择它 + +至于判断的标准很简单,就是价值最大,因此我们要做的就是对于选择和不选择两种情况分别求价值,然后取最大,最后更新 cell 即可。 + +其实大部分的动态规划问题套路都是“选择”或者“不选择”,也就是说是一种“选择题”。 并且大多数动态规划题目还伴随着空间的优化,这是动态规划相对于传统的记忆化递归优势的地方。除了这点优势,就是上文提到的使用动态规划可以减少递归产生的函数调用栈,因此性能上更好。 + +### 相关问题 + +- [0091.decode-ways](../problems/91.decode-ways.md) +- [0139.word-break](../problems/139.word-break.md) +- [0198.house-robber](../problems/0198.house-robber.md) +- [0309.best-time-to-buy-and-sell-stock-with-cooldown](../problems/309.best-time-to-buy-and-sell-stock-with-cooldown.md) +- [0322.coin-change](../problems/322.coin-change.md) +- [0416.partition-equal-subset-sum](../problems/416.partition-equal-subset-sum.md) +- [0518.coin-change-2](../problems/518.coin-change-2.md) + +> 太多了,没有逐一列举 + +## 总结 + +本篇文章总结了算法中比较常用的两个方法 - 递归和动态规划。 + +如果你只能记住一句话,那么请记住:`递归是从问题的结果倒推,直到问题的规模缩小到寻常。 动态规划是从寻常入手, 逐步扩大规模到最优子结构。` + +另外,大家可以去 LeetCode 探索中的 [递归 I](https://leetcode-cn.com/explore/orignial/card/recursion-i/) 中进行互动式学习。 diff --git a/thinkings/greedy.md b/thinkings/greedy.md new file mode 100644 index 0000000..179ac31 --- /dev/null +++ b/thinkings/greedy.md @@ -0,0 +1,269 @@ +# 贪婪策略 + +贪婪策略是一种常见的算法思想,具体是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关,这点和动态规划一样。贪婪策略和动态规划类似,大多数情况也都是用来处理`极值问题`。 + +LeetCode 上对于贪婪策略有 73 道题目。我们将其分成几个类型来讲解,截止目前我们暂时只提供`覆盖`问题,其他的可以期待我的新书或者之后的题解文章。 + +## 覆盖 + +我们挑选三道来讲解,这三道题除了使用贪婪法,你也可以尝试动态规划来解决。 + +- [45. 跳跃游戏 II](https://leetcode-cn.com/problems/jump-game-ii/),困难 +- [1024. 视频拼接](https://leetcode-cn.com/problems/video-stitching/),中等 +- [1326. 灌溉花园的最少水龙头数目](https://leetcode-cn.com/problems/minimum-number-of-taps-to-open-to-water-a-garden/),困难 + +覆盖问题的一大特征,我们可以将其抽象为`给定数轴上的一个大区间 I 和 n 个小区间 i[0], i[1], ..., i[n - 1],问最少选择多少个小区间,使得这些小区间的并集可以覆盖整个大区间。` + +我们来看下这三道题吧。 + +### 45. 跳跃游戏 II + +#### 题目描述 + +给定一个非负整数数组,你最初位于数组的第一个位置。 + +数组中的每个元素代表你在该位置可以跳跃的最大长度。 + +你的目标是使用最少的跳跃次数到达数组的最后一个位置。 + +示例: + +输入: [2,3,1,1,4] +输出: 2 +解释: 跳到最后一个位置的最小跳跃数是 2。 +  从下标为 0 跳到下标为 1 的位置,跳  1  步,然后跳  3  步到达数组的最后一个位置。 +说明: + +假设你总是可以到达数组的最后一个位置。 + +#### 思路 + +贪婪策略,即我们每次在可跳范围内选择可以使得跳的更远的位置,由于题目保证了`你总是可以到达数组的最后一个位置`,因此这种算法是完备的。 + +如下图,开始的位置是 2,可跳的范围是橙色的。然后因为 3 可以跳的更远,所以跳到 3 的位置。 + +![](https://tva1.sinaimg.cn/large/0082zybply1gc0ymvsw64j309i03xmx7.jpg) + +如下图,然后现在的位置就是 3 了,能跳的范围是橙色的,然后因为 4 可以跳的更远,所以下次跳到 4 的位置。 + +![](https://tva1.sinaimg.cn/large/0082zybply1gc0ynd8zilj30c10390ss.jpg) + +写代码的话,我们用 end 表示当前能跳的边界,对于上边第一个图的橙色 1,第二个图中就是橙色的 4,遍历数组的时候,到了边界,我们就重新更新新的边界。 + +> 图来自 https://leetcode-cn.com/u/windliang/ + +#### 代码 + +代码支持:Python3 + +Python3 Code: + +```python +class Solution: + def jump(self, nums: List[int]) -> int: + n, cnt, furthest, end = len(nums), 0, 0, 0 + for i in range(n - 1): + furthest = max(furthest, nums[i] + i) + if i == end: + cnt += 1 + end = furthest + + return cnt +``` + +**复杂度分析** + +- 时间复杂度:$O(N)$。 + +- 空间复杂度:$O(1)$。 + +### 1024. 视频拼接 + +#### 题目描述 + +你将会获得一系列视频片段,这些片段来自于一项持续时长为  T  秒的体育赛事。这些片段可能有所重叠,也可能长度不一。 + +视频片段  clips[i]  都用区间进行表示:开始于  clips[i][0]  并于  clips[i][1]  结束。我们甚至可以对这些片段自由地再剪辑,例如片段  [0, 7]  可以剪切成  [0, 1] + [1, 3] + [3, 7]  三部分。 + +我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段([0, T])。返回所需片段的最小数目,如果无法完成该任务,则返回  -1 。 + +示例 1: + +输入:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], T = 10 +输出:3 +解释: +我们选中 [0,2], [8,10], [1,9] 这三个片段。 +然后,按下面的方案重制比赛片段: +将 [1,9] 再剪辑为 [1,2] + [2,8] + [8,9] 。 +现在我们手上有 [0,2] + [2,8] + [8,10],而这些涵盖了整场比赛 [0, 10]。 +示例 2: + +输入:clips = [[0,1],[1,2]], T = 5 +输出:-1 +解释: +我们无法只用 [0,1] 和 [0,2] 覆盖 [0,5] 的整个过程。 +示例 3: + +输入:clips = [[0,1],[6,8],[0,2],[5,6],[0,4],[0,3],[6,7],[1,3],[4,7],[1,4],[2,5],[2,6],[3,4],[4,5],[5,7],[6,9]], T = 9 +输出:3 +解释: +我们选取片段 [0,4], [4,7] 和 [6,9] 。 +示例 4: + +输入:clips = [[0,4],[2,8]], T = 5 +输出:2 +解释: +注意,你可能录制超过比赛结束时间的视频。 + +提示: + +1 <= clips.length <= 100 +0 <= clips[i][0], clips[i][1] <= 100 +0 <= T <= 100 + +#### 思路 + +贪婪策略,我们选择满足条件的最大值。和上面的不同,这次我们需要手动进行一次排序,实际上贪婪策略经常伴随着排序,我们按照 clip[0]从小到大进行排序。 + +![](https://tva1.sinaimg.cn/large/0082zybply1gc0yseg71aj30yg0i0js3.jpg) + +如图: + +- 1 不可以,因此存在断层 +- 2 可以 +- 3 不行,因为不到 T + +我们当前的 clip 开始结束时间分别为 s,e。 上一段 clip 的结束时间是 t1,上上一段 clip 结束时间是 t2。 + +那么这种情况下 t1 实际上是不需要的,因为 t2 完全可以覆盖它: + +![](https://tva1.sinaimg.cn/large/0082zybply1gc0ywpgkcsj30o604sq2w.jpg) + +那什么样 t1 才是需要的呢?如图: + +![](https://tva1.sinaimg.cn/large/0082zybply1gc0yxinwf7j30mc05sgll.jpg) + +用代码来说的话就是`s > t2 and t2 <= t1` + +#### 代码 + +代码支持:Python3 + +Python3 Code: + +```python + +class Solution: + def videoStitching(self, clips: List[List[int]], T: int) -> int: + # t1 表示选取的上一个clip的结束时间 + # t2 表示选取的上上一个clip的结束时间 + t2, t1, cnt = -1, 0, 0 + clips.sort(key=lambda a: a[0]) + for s, e in clips: + # s > t1 已经确定不可以了, t1 >= T 已经可以了 + if s > t1 or t1 >= T: + break + if s > t2 and t2 <= t1: + cnt += 1 + t2 = t1 + t1 = max(t1,e) + return cnt if t1 >= T else - 1 + +``` + +**复杂度分析** + +- 时间复杂度:由于使用了排序(假设是基于比较的排序),因此时间复杂度为 $O(NlogN)$。 + +- 空间复杂度:$O(1)$。 + +### 1326. 灌溉花园的最少水龙头数目 + +#### 题目描述 + +在 x 轴上有一个一维的花园。花园长度为  n,从点  0  开始,到点  n  结束。 + +花园里总共有  n + 1 个水龙头,分别位于  [0, 1, ..., n] 。 + +给你一个整数  n  和一个长度为  n + 1 的整数数组  ranges ,其中  ranges[i] (下标从 0 开始)表示:如果打开点  i  处的水龙头,可以灌溉的区域为  [i -  ranges[i], i + ranges[i]] 。 + +请你返回可以灌溉整个花园的   最少水龙头数目  。如果花园始终存在无法灌溉到的地方,请你返回  -1 。 + +示例 1: + +![](https://tva1.sinaimg.cn/large/0082zybply1gc0z68dxoxj30bm05xjrk.jpg) + +输入:n = 5, ranges = [3,4,1,1,0,0] +输出:1 +解释: +点 0 处的水龙头可以灌溉区间 [-3,3] +点 1 处的水龙头可以灌溉区间 [-3,5] +点 2 处的水龙头可以灌溉区间 [1,3] +点 3 处的水龙头可以灌溉区间 [2,4] +点 4 处的水龙头可以灌溉区间 [4,4] +点 5 处的水龙头可以灌溉区间 [5,5] +只需要打开点 1 处的水龙头即可灌溉整个花园 [0,5] 。 +示例 2: + +输入:n = 3, ranges = [0,0,0,0] +输出:-1 +解释:即使打开所有水龙头,你也无法灌溉整个花园。 +示例 3: + +输入:n = 7, ranges = [1,2,1,0,2,1,0,1] +输出:3 +示例 4: + +输入:n = 8, ranges = [4,0,0,0,0,0,0,0,4] +输出:2 +示例 5: + +输入:n = 8, ranges = [4,0,0,0,4,0,0,0,4] +输出:1 + +提示: + +1 <= n <= 10^4 +ranges.length == n + 1 +0 <= ranges[i] <= 100 + +#### 思路 + +贪心策略,我们尽量找到能够覆盖最远(右边)位置的水龙头,并记录它最右覆盖的土地。 + +- 我们使用 furthest[i] 来记录经过每一个水龙头 i 能够覆盖的最右侧土地。 +- 一共有 n+1 个水龙头,我们遍历 n + 1 次。 +- 对于每次我们计算水龙头的左右边界,[i - ranges[i], i + ranges[i]] +- 我们更新左右边界范围内的水龙头的 furthest +- 最后从土地 0 开始,一直到土地 n ,记录水龙头数目 + +#### 代码 + +代码支持:Python3 + +Python3 Code: + +```python + +class Solution: + def minTaps(self, n: int, ranges: List[int]) -> int: + furthest, cnt, cur = [0] * n, 0, 0 + + for i in range(n + 1): + l = max(0, i - ranges[i]) + r = min(n, i + ranges[i]) + for j in range(l, r): + furthest[j] = max(furthest[j], r) + while cur < n: + if furthest[cur] == 0: return -1 + cur = furthest[cur] + cnt += 1 + return cnt + +``` + +**复杂度分析** + +- 时间复杂度:时间复杂度取决 l 和 r,也就是说取决于 ranges 数组的值,假设 ranges 的平均大小为 Size 的话,那么时间复杂度为 $O(N \* Size)$。 + +- 空间复杂度:我们使用了 furthest 数组, 因此空间复杂度为 $O(N)$。 diff --git a/thinkings/island.md b/thinkings/island.md new file mode 100644 index 0000000..e07608e --- /dev/null +++ b/thinkings/island.md @@ -0,0 +1,9 @@ +# 小岛 + +LeetCode 上有很多小岛题,虽然官方没有这个标签, 但是在我这里都差不多。不管是思路还是套路都比较类似,大家可以结合起来练习。 + +- [200. 岛屿数量](https://github.com/azl397985856/leetcode/blob/master/problems/200.number-of-islands.md) +- [695. 岛屿的最大面积](https://leetcode-cn.com/problems/max-area-of-island/solution/695-dao-yu-de-zui-da-mian-ji-dfspython3-by-fe-luci/) +- [1162. 地图分析](https://leetcode-cn.com/problems/as-far-from-land-as-possible/solution/python-tu-jie-chao-jian-dan-de-bfs1162-di-tu-fen-x/) + +上面三道题都可以使用常规的 DFS 来做。 并且递归的方向都是上下左右四个方向。更有意思的是,都可以采用原地修改的方式,来减少开辟 visited 的空间。 diff --git a/thinkings/prefix.md b/thinkings/prefix.md new file mode 100644 index 0000000..6adb11f --- /dev/null +++ b/thinkings/prefix.md @@ -0,0 +1,4 @@ +## 题目列表 + +- [掌握前缀表达式真的可以为所欲为!](https://lucifer.ren/blog/2020/01/09/1310.xor-queries-of-a-subarray/) +- [1371.find-the-longest-substring-containing-vowels-in-even-counts](../problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md) 🆕 diff --git a/thinkings/run-length-encode-and-huffman-encode.md b/thinkings/run-length-encode-and-huffman-encode.md new file mode 100644 index 0000000..c1a11e4 --- /dev/null +++ b/thinkings/run-length-encode-and-huffman-encode.md @@ -0,0 +1,92 @@ +# 游程编码和哈夫曼编码 + +## Huffman encode(哈夫曼编码) + +Huffman 编码的基本思想就是用短的编码表示出现频率高的字符,用长的编码来表示出现频率低的字符,这使得编码之后的字符串的平均长度、长度的期望值降低,从而实现压缩的目的。 +因此 Huffman 编码被广泛地应用于无损压缩领域。 + +Huffman 编码的过程包含两个主要部分: + +- 根据输入字符构建 Huffman 树 +- 遍历 Huffman 树,并将树的节点分配给字符 + +上面提到了他的基本原理就是`用短的编码表示出现频率高的字符,用长的编码来表示出现频率低的字符`, +因此首先要做的就是统计字符的出现频率,然后根据统计的频率来构建 Huffman 树(又叫最优二叉树)。 + +![Huffman-tree](../assets/thinkings/huffman-tree.webp) + +Huffman 树就像是一个堆。真正执行编码的时候,类似字典树,节点不用来编码,节点的路径用来编码. + +> 节点的值只是用来构建 Huffman 树 + +eg: + +我们统计的结果如下: + +|character|frequency| +|:--:|:--:| +|a|5| +|b|9| +|c|12| +|d|13| +|e|16| +|f|45| + +- 将每个元素构造成一个节点,即只有一个元素的树。并构建一个最小堆,包含所有的节点,该算法用了最小堆来作为优先队列。 + +- `选取两个权值最小的节点`,并添加一个权值为5+9=14的节点,作为他们的父节点。并`更新最小堆`,现在最小堆包含5个节点,其中4个树还是原来的节点,权值为5和9的节点合并为一个。 + +结果是这样的: + +![huffman-example](../assets/thinkings/huffman-example-fix.png) + +|character|frequency|encoding| +|:-:|:-:|:-:| +|a|5|1100| +|b|9|1101| +|c|12|100| +|d|13|101| +|e|16|111| +|f|45|0| + +## run-length encode(游程编码) + +游程编码是一种比较简单的压缩算法,其基本思想是将重复且连续出现多次的字符使用(连续出现次数,某个字符)来描述。 + +比如一个字符串: + +```text +AAAAABBBBCCC +``` + +使用游程编码可以将其描述为: + +```text +5A4B3C +``` + +5A表示这个地方有5个连续的A,同理4B表示有4个连续的B,3C表示有3个连续的C,其它情况以此类推。 + +但是实际上情况可能会非常复杂, 如何提取子序列有时候没有看的那么简单,还是上面的例子,我们 +有时候可以把`AAAAABBBBCCC`整体看成一个子序列, 更复杂的情况还有很多,这里不做扩展。 + +对文件进行压缩比较适合的情况是文件内的二进制有大量的连续重复, +一个经典的例子就是具有大面积色块的BMP图像,BMP因为没有压缩, +所以看到的是什么样子存储的时候二进制就是什么样子 + +> 这也是我们图片倾向于纯色的时候,压缩会有很好的效果 + +> 思考一个问题, 如果我们在CDN上存储两个图片,这两个图片几乎完全一样,我们是否可以进行优化呢? +这虽然是CDN厂商更应该关心的问题,但是这个问题对我们影响依然很大,值得思考 + +## 总结 + +游程编码和Huffman都是无损压缩算法,即解压缩过程不会损失原数据任何内容。 实际情况,我们先用游程编码一遍,然后再用 Huffman 再次编码一次。几乎所有的无损压缩格式都用到了它们,比如PNG,GIF,PDF,ZIP等。 + +对于有损压缩,通常是去除了人类无法识别的颜色,听力频率范围等。也就是说损失了原来的数据。 但由于人类无法识别这部分信息,因此很多情况下都是值得的。这种删除了人类无法感知内容的编码,我们称之为“感知编码”(也许是一个自创的新名词),比如JPEG,MP3等。关于有损压缩不是本文的讨论范围,感兴趣的可以搜素相关资料。 + +实际上,视频压缩的原理也是类似,只不过视频压缩会用到一些额外的算法,比如“时间冗余”,即仅存储变化的部分,对于不变的部分,存储一次就够了。 + +## 相关题目 + +[900.rle-iterator](../problems/900.rle-iterator.md) diff --git a/thinkings/slide-window.en.md b/thinkings/slide-window.en.md new file mode 100644 index 0000000..b18a7b9 --- /dev/null +++ b/thinkings/slide-window.en.md @@ -0,0 +1,87 @@ +# Sliding Window Technique + +I first encountered the term "sliding window" when learning about the sliding window protocols, which is used in Transmission Control Protocol (TCP) for packet-based data transimission. It is used to improved transmission efficiency in order to avoid congestions. The sender and the receiver each has a window size, w1 and w2, respectively. The window size may vary based on the network traffic flow. However, in a simpler implementation, the sizes are fixed, and they must be greater than 0 to perform any task. + +The sliding window technique in algorithms is very similar, but it applies to more scenarios. Now, let's go over this technique. + +## Introduction + +Sliding window technique, also known as two pointers technique, can help reduce time complexity in problems that ask for "consecutive" or "contiguous" items. For example, [209. Minimum Size Subarray Sum](https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/209-chang-du-zui-xiao-de-zi-shu-zu-hua-dong-chua-2/). For more related problems, go to the `List of Problems` below. + +## Common Types + +This technique is mainly for solving problems ask about "consecutive substring" or "contiguous subarray". It would be helpful if you can relate these terms with this technique in your mind. Whether the technique solve the exact problem or not, it would come in handy. + +There are mainly three types of application: + +- Fixed window size +- Variable window size and looking for the maximum window size that meet the requirement +- Variable window size and looking for the minimum window size that meet the requirement (e.g. Problem#209 mentioned above) + +The last two are catogorized as "variable window". Of course, they are all of the same essentially. It's all about the implementation details. + +### Fixed Window Size + +For fixed window size problem, we only need to keep track of the left pointer l and the right pointer r, which indicate the boundaries of a fixed window, and make sure that: + +1. l is initialized to be 0 +2. r is initialied such that the window's size = r - l + 1 +3. Always move l and r simultaneously +4. Decide if the consecutive elements contained within the window satisfy the required conditions. + - 4.1 If they satisfy, based on whether we need an optimal solution or not, we either return the solution or keep updating until we find the optimal one. + - 4.2 Otherwise, we continue to find an appropriate window + +![](https://tva1.sinaimg.cn/large/00831rSTly1gcw0pwdhmwj308z0d53yt.jpg) + +### Variable Window Size + +For variable window, we initialize the left and right pointers the same way. Then we need to make sure that: + +1. Both l and r are initialized to 0 +2. Move r to the right by one step +3. Decide if the consecutive elements contained within the window satisfy the required conditions + - 3.1 If they satisfy + - 3.1.1 and we need an optimal solution, we try moving the pointer l to minimize our window's size and repeat step 3.1 + - 3.1.2 else we return the current solution + - 3.2 If they don't satisfy, we continue to find an appropriate window + +If we view it another way, it's simply moving the pointer r to find an appropriate window and we only move the pointer l once we find an appropriate window to minimize the window and find an optimal solution. + +![](https://tva1.sinaimg.cn/large/00831rSTly1gcw0ouuplaj30d90d50t3.jpg) + +## Code Template + +The following code snippet is a solution for problem #209 written in Python. + +```python +class Solution: + def minSubArrayLen(self, s: int, nums: List[int]) -> int: + l = total = 0 + ans = len(nums) + 1 + for r in range(len(nums)): + total += nums[r] + while total >= s: + ans = min(ans, r - l + 1) + total -= nums[l] + l += 1 + return 0 if ans == len(nums) + 1 else ans +``` + +## List of problems (Not Translated Yet) + +Some problems here are intuitive that you know the sliding window technique would be useful while others need a second thought to realize that. + +- [【Python,JavaScript】滑动窗口(3. 无重复字符的最长子串)](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/pythonjavascript-hua-dong-chuang-kou-3-wu-zhong-fu/) +- [76. 最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/solution/python-hua-dong-chuang-kou-76-zui-xiao-fu-gai-zi-c/) +- [209. 长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/209-chang-du-zui-xiao-de-zi-shu-zu-hua-dong-chua-2/) +- [【Python】滑动窗口(438. 找到字符串中所有字母异位词)](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/solution/python-hua-dong-chuang-kou-438-zhao-dao-zi-fu-chua/) +- [【904. 水果成篮】(Python3)](https://leetcode-cn.com/problems/fruit-into-baskets/solution/904-shui-guo-cheng-lan-python3-by-fe-lucifer/) +- [【930. 和相同的二元子数组】(Java,Python)](https://leetcode-cn.com/problems/binary-subarrays-with-sum/solution/930-he-xiang-tong-de-er-yuan-zi-shu-zu-javapython-/) +- [【992. K 个不同整数的子数组】滑动窗口(Python)](https://leetcode-cn.com/problems/subarrays-with-k-different-integers/solution/992-k-ge-bu-tong-zheng-shu-de-zi-shu-zu-hua-dong-c/) +- [【1004. 最大连续 1 的个数 III】滑动窗口(Python3)](https://leetcode-cn.com/problems/max-consecutive-ones-iii/solution/1004-zui-da-lian-xu-1de-ge-shu-iii-hua-dong-chuang/) +- [【1234. 替换子串得到平衡字符串】[Java/C++/Python] Sliding Window](https://leetcode.com/problems/replace-the-substring-for-balanced-string/discuss/408978/javacpython-sliding-window/367697) +- [【1248. 统计「优美子数组」】滑动窗口(Python)](https://leetcode-cn.com/problems/count-number-of-nice-subarrays/solution/1248-tong-ji-you-mei-zi-shu-zu-hua-dong-chuang-kou/) + +## Further Readings + +- [LeetCode Sliding Window Series Discussion](https://leetcode.com/problems/binary-subarrays-with-sum/discuss/186683/)(English) diff --git a/thinkings/slide-window.md b/thinkings/slide-window.md new file mode 100644 index 0000000..72dfdd9 --- /dev/null +++ b/thinkings/slide-window.md @@ -0,0 +1,99 @@ +# 滑动窗口(Sliding Window) + +笔者最早接触滑动窗口是`滑动窗口协议`,滑动窗口协议(Sliding Window Protocol),属于 TCP 协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。 发送方和接收方分别有一个窗口大小 w1 和 w2。窗口大小可能会根据网络流量的变化而有所不同,但是在更简单的实现中它们是固定的。窗口大小必须大于零才能进行任何操作。 + +我们算法中的滑动窗口也是类似,只不过包括的情况更加广泛。实际上上面的滑动窗口在某一个时刻就是固定窗口大小的滑动窗口,随着网络流量等因素改变窗口大小也会随着改变。接下来我们讲下算法中的滑动窗口。 + +## 介绍 + +滑动窗口是一种解决问题的思路和方法,通常用来解决一些连续问题。 比如 LeetCode 的 [209. 长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/209-chang-du-zui-xiao-de-zi-shu-zu-hua-dong-chua-2/)。更多滑动窗口题目见下方`题目列表`。 + +## 常见套路 + +滑动窗口主要用来处理连续问题。比如题目求解“连续子串 xxxx”,“连续子数组 xxxx”,就应该可以想到滑动窗口。能不能解决另说,但是这种敏感性还是要有的。 + +从类型上说主要有: + +- 固定窗口大小 +- 窗口大小不固定,求解最大的满足条件的窗口 +- 窗口大小不固定,求解最小的满足条件的窗口(上面的 209 题就属于这种) + +后面两种我们统称为`可变窗口`。当然不管是哪种类型基本的思路都是一样的,不一样的仅仅是代码细节。 + +### 固定窗口大小 + +对于固定窗口,我们只需要固定初始化左右指针 l 和 r,分别表示的窗口的左右顶点,并且保证: + +1. l 初始化为 0 +2. 初始化 r,使得 r - l + 1 等于窗口大小 +3. 同时移动 l 和 r +4. 判断窗口内的连续元素是否满足题目限定的条件 + - 4.1 如果满足,再判断是否需要更新最优解,如果需要则更新最优解 + - 4.2 如果不满足,则继续。 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gcw0pwdhmwj308z0d53yt.jpg) + +### 可变窗口大小 + +对于可变窗口,我们同样固定初始化左右指针 l 和 r,分别表示的窗口的左右顶点。后面有所不同,我们需要保证: + +1. l 和 r 都初始化为 0 +2. r 指针移动一步 +3. 判断窗口内的连续元素是否满足题目限定的条件 + - 3.1 如果满足,再判断是否需要更新最优解,如果需要则更新最优解。并尝试通过移动 l 指针缩小窗口大小。循环执行 3.1 + - 3.2 如果不满足,则继续。 + +形象地来看的话,就是 r 指针不停向右移动,l 指针仅仅在窗口满足条件之后才会移动,起到窗口收缩的效果。 + +![](https://tva1.sinaimg.cn/large/00831rSTly1gcw0ouuplaj30d90d50t3.jpg) + +## 模板代码 + +### 伪代码 +``` +初始化慢指针 = 0 +初始化 ans + +for 快指针 in 可迭代集合 + 更新窗口内信息 + while 窗口内不符合题意 + 扩展或者收缩窗口 + 慢指针移动 +返回 ans +``` +### 代码 + +以下是 209 题目的代码,使用 Python 编写,大家意会即可。 + +```python +class Solution: + def minSubArrayLen(self, s: int, nums: List[int]) -> int: + l = total = 0 + ans = len(nums) + 1 + for r in range(len(nums)): + total += nums[r] + while total >= s: + ans = min(ans, r - l + 1) + total -= nums[l] + l += 1 + return 0 if ans == len(nums) + 1 else ans +``` + +## 题目列表 + +以下题目有的信息比较直接,有的题目信息比较隐蔽,需要自己发掘 + +- [【Python,JavaScript】滑动窗口(3. 无重复字符的最长子串)](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/pythonjavascript-hua-dong-chuang-kou-3-wu-zhong-fu/) +- [76. 最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/solution/python-hua-dong-chuang-kou-76-zui-xiao-fu-gai-zi-c/) +- [209. 长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/209-chang-du-zui-xiao-de-zi-shu-zu-hua-dong-chua-2/) +- [【Python】滑动窗口(438. 找到字符串中所有字母异位词)](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/solution/python-hua-dong-chuang-kou-438-zhao-dao-zi-fu-chua/) +- [【904. 水果成篮】(Python3)](https://leetcode-cn.com/problems/fruit-into-baskets/solution/904-shui-guo-cheng-lan-python3-by-fe-lucifer/) +- [【930. 和相同的二元子数组】(Java,Python)](https://leetcode-cn.com/problems/binary-subarrays-with-sum/solution/930-he-xiang-tong-de-er-yuan-zi-shu-zu-javapython-/) +- [【992. K 个不同整数的子数组】滑动窗口(Python)](https://leetcode-cn.com/problems/subarrays-with-k-different-integers/solution/992-k-ge-bu-tong-zheng-shu-de-zi-shu-zu-hua-dong-c/) +- [【1004. 最大连续 1 的个数 III】滑动窗口(Python3)](https://leetcode-cn.com/problems/max-consecutive-ones-iii/solution/1004-zui-da-lian-xu-1de-ge-shu-iii-hua-dong-chuang/) +- [【1234. 替换子串得到平衡字符串】[Java/C++/Python] Sliding Window](https://leetcode.com/problems/replace-the-substring-for-balanced-string/discuss/408978/javacpython-sliding-window/367697) +- [【1248. 统计「优美子数组」】滑动窗口(Python)](https://leetcode-cn.com/problems/count-number-of-nice-subarrays/solution/1248-tong-ji-you-mei-zi-shu-zu-hua-dong-chuang-kou/) + +## 扩展阅读 + +- [LeetCode Sliding Window Series Discussion](https://leetcode.com/problems/binary-subarrays-with-sum/discuss/186683/) diff --git a/thinkings/string-problems-en.md b/thinkings/string-problems-en.md new file mode 100644 index 0000000..5cf6ca7 --- /dev/null +++ b/thinkings/string-problems-en.md @@ -0,0 +1,44 @@ +# Problems about String + +There are many problems about string, including `substr` implementation, validating palindrome and common substring and so on. Essentially, a string is also an array of characters. So, many ideas of array can be used to solve the problems of string. + +There are many algorithms which specifically used for handle strings. Such as `trie`, `huffman tree`, `Manacher` and so on. + +## Problems about Implementing Build-in Functions of String + +This kind of questions are the most direct ones with less ambiguous meanings and less challenging. So, it is always be used in the phone interviews. + +- [28.implement-str-str](https://leetcode.com/problems/implement-strstr/) +- [344.reverse-string](../backlog/344.reverse-string.js) + +## Palindrome + +A palindrome is a word, number, phrase, or other sequence of characters which reads the same backward as forward. Like "level" and "noon". + +There is universal method to check whether a string is palindrome or not which uses two pointers, one at the begining and the other at the end, to move to the middle together step by step. You can check the following question `125`for more detials. +For finding the longest palindrome, it is possible to reduce many meaningless algorithms if we take full advantage of the feature of palindrome. Manacher's algorithm is a typical example. + +### Related Questions + +- [5.longest-palindromic-substring](../problems/5.longest-palindromic-substring.md) + +- [125.valid-palindrome](../problems/125.valid-palindrome.md) + +- [131.palindrome-partitioning](../problems/131.palindrome-partitioning.md) + +- [shortest-palindrome](https://leetcode.com/problems/shortest-palindrome/) + +- [516.longest-palindromic-subsequence](../problems/516.longest-palindromic-subsequence.md) + +## Prefix Questions + +It is intuitive to use prefix tree to handle this kind of questions. But it also has some disadvantages. For example, if there are less common prefix, using prefix tree may cost more RAMs. + +### Related Questions + +-[14.longest-common-prefix](../14.longest-common-prefix.js) +-[208.implement-trie-prefix-tree](../problems/208.implement-trie-prefix-tree.md) + +## Other Questions + +- [139.word-break](../problems/139.word-break.md) diff --git a/thinkings/string-problems.md b/thinkings/string-problems.md new file mode 100644 index 0000000..764a592 --- /dev/null +++ b/thinkings/string-problems.md @@ -0,0 +1,49 @@ +## 字符串问题 + +字符串问题有很多,从简单的实现substr,识别回文,到复杂一点的公共子串/子序列。其实字符串本质上也是字符数组,因此 +很多数据的思想和方法也可以用在字符串问题上,并且在有些时候能够发挥很好的作用。 + +专门处理字符串的算法也很多,比如trie,马拉车算法,游程编码,huffman树等等。 + + +## 实现字符串的一些原生方法 + +这类题目应该是最直接的题目了,题目歧义比较小, 难度也是相对较小,因此用于电面等形式也是不错的。 + +- [28.implement-str-str](https://leetcode.com/problems/implement-strstr/) +- [344.reverse-string](../backlog/344.reverse-string.js) + +## 回文 + +回文串就是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。 + +判断是否回文的通用方法是首尾双指针,具体可以见下方125号题目。 判断最长回文的思路主要是两个字"扩展", +如果可以充分利用回文的特点,则可以减少很多无谓的计算,典型的是《马拉车算法》。 + + +### 相关问题 + +- [5.longest-palindromic-substring](../problems/5.longest-palindromic-substring.md) + +- [125.valid-palindrome](../problems/125.valid-palindrome.md) + +- [131.palindrome-partitioning](../problems/131.palindrome-partitioning.md) + +- [shortest-palindrome](https://leetcode.com/problems/shortest-palindrome/) + +- [516.longest-palindromic-subsequence](../problems/516.longest-palindromic-subsequence.md) + + +## 前缀问题 + +前缀树用来处理这种问题是最符合直觉的,但是它也有缺点,比如公共前缀很少的情况下,比较费内存。 + +### 相关题目 + +-[14.longest-common-prefix](../14.longest-common-prefix.js) +-[208.implement-trie-prefix-tree](../problems/208.implement-trie-prefix-tree.md) + + +## 其他问题 + +- [139.word-break](../problems/139.word-break.md) \ No newline at end of file diff --git a/thinkings/trie.md b/thinkings/trie.md new file mode 100644 index 0000000..c9ff2fe --- /dev/null +++ b/thinkings/trie.md @@ -0,0 +1,27 @@ +## 前缀树问题 + +截止目前(2020-02-04) [前缀树(字典树)](https://leetcode-cn.com/tag/trie/) 在 LeetCode 一共有 17 道题目。其中 2 道简单,8 个中等,7 个困难。 + +这里总结了四道题,弄懂这几道, 那么前缀树对你应该不是大问题, 希望这个专题可以帮到正在学习前缀树 的你。 + +前缀树的 api 主要有以下几个: + +- `insert(word)`: 插入一个单词 +- `search(word)`:查找一个单词是否存在 +- `startWith(word)`: 查找是否存在以 word 为前缀的单词 + +其中 startWith 是前缀树最核心的用法,其名称前缀树就从这里而来。大家可以先拿 208 题开始,熟悉一下前缀树,然后再尝试别的题目。 + +一个前缀树大概是这个样子: + +![](https://github.com/azl397985856/leetcode/raw/b8e8fa5f0554926efa9039495b25ed7fc158372a/assets/problems/208.implement-trie-prefix-tree-1.png) + +如图每一个节点存储一个字符,然后外加一个控制信息表示是否是单词结尾,实际使用过程可能会有细微差别,不过变化不大。 + +以下是本专题的四道题目的题解,内容会持续更新,感谢你的关注~ + +- [0208.implement-trie-prefix-tree](https://github.com/azl397985856/leetcode/blob/b8e8fa5f0554926efa9039495b25ed7fc158372a/problems/208.implement-trie-prefix-tree.md) +- [0211.add-and-search-word-data-structure-design](https://github.com/azl397985856/leetcode/blob/b0b69f8f11dace3a9040b54532105d42e88e6599/problems/211.add-and-search-word-data-structure-design.md) +- [0212.word-search-ii](https://github.com/azl397985856/leetcode/blob/b0b69f8f11dace3a9040b54532105d42e88e6599/problems/212.word-search-ii.md) +- [0472.concatenated-words](https://github.com/azl397985856/leetcode/blob/master/problems/472.concatenated-words.md) +- [0820.short-encoding-of-words](https://github.com/azl397985856/leetcode/blob/master/problems/820.short-encoding-of-words.md) diff --git a/thinkings/union-find.md b/thinkings/union-find.md new file mode 100644 index 0000000..972975f --- /dev/null +++ b/thinkings/union-find.md @@ -0,0 +1,146 @@ +# 并查集 + +关于并查集的题目不少,官方给的数据是 30 道(截止 2020-02-20),但是有一些题目虽然官方没有贴`并查集`标签,但是使用并查集来说确非常简单。这类题目如果掌握模板,那么刷这种题会非常快,并且犯错的概率会大大降低,这就是模板的好处。 + +我这里总结了几道并查集的题目: + +- [547. 朋友圈](../problems/547.friend-circles.md) +- [721. 账户合并](https://leetcode-cn.com/problems/accounts-merge/solution/mo-ban-ti-bing-cha-ji-python3-by-fe-lucifer-3/) +- [990. 等式方程的可满足性](https://github.com/azl397985856/leetcode/issues/304) + +看完这里的内容,建议拿上面的题目练下手,检测一下学习成果。 + +## 概述 + +并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作: + +- Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。 +- Union:将两个子集合并成同一个集合。 + +由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数。 + +## 形象解释 + +比如有两个司令。 司令下有若干军长,军长下有若干师长。。。 + +我们如何判断某两个师长是否属于同一个司令呢(连通性)? + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ge1ap6p77yj30gs0bz3zn.jpg) + +很简单,我们顺着师长,往上找,找到司令。 如果两个师长找到的是同一个司令,那么就属于同一个司令。我们用 parent[x] = y 表示 x 的 parent 是 y,通过不断沿着搜索 parent 搜索找到 root,然后比较 root 是否相同即可得出结论。 + +以上过程涉及了两个基本操作`find`和`connnected`。 并查集除了这两个基本操作,还有一个是`union`。即将两个集合合并为同一个。 + +如图有两个司令: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ge1auw6z8oj30wp0eljth.jpg) + +我们将其合并为一个联通域,最简单的方式就是直接将其中一个司令指向另外一个即可: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1ge1awrmaclj30ym0cogo4.jpg) + +以上就是三个核心 API `find`,`connnected` 和 `union`, 的形象化解释,下面我们来看下代码实现。 + +## 核心 API + +### find + +```python +def find(self, x): + while x != self.parent[x]: + x = self.parent[x] + return x +``` + +### connected + +```python +def connected(self, p, q): + return self.find(p) == self.find(q) +``` + +### union + +```python +def union(self, p, q): + if self.connected(p, q): return + self.parent[self.find(p)] = self.find(q) +``` + +## 完整代码模板 + +```python +class UF: + parent = {} + cnt = 0 + def __init__(self, M): + # 初始化 parent 和 cnt + + def find(self, x): + while x != self.parent[x]: + x = self.parent[x] + return x + def union(self, p, q): + if self.connected(p, q): return + self.parent[self.find(p)] = self.find(q) + self.cnt -= 1 + def connected(self, p, q): + return self.find(p) == self.find(q) +``` + +## 带路径压缩的代码模板 + +```python +class UF: + parent = {} + size = {} + cnt = 0 + def __init__(self, M): + # 初始化 parent,size 和 cnt + + def find(self, x): + while x != self.parent[x]: + x = self.parent[x] + # 路径压缩 + self.parent[x] = self.parent[self.parent[x]]; + return x + def union(self, p, q): + if self.connected(p, q): return + # 小的树挂到大的树上, 使树尽量平衡 + leader_p = self.find(p) + leader_q = self.find(q) + if self.size[leader_p] < self.size[leader_q]: + self.parent[leader_p] = leader_q + else: + self.parent[leader_q] = leader_p + self.cnt -= 1 + def connected(self, p, q): + return self.find(p) == self.find(q) +``` + +上面是递归的方式进行路径压缩,写起来比较简单。但是有栈溢出的风险。 接下来我们看下迭代的写法: + +```python +class UF: + parent = {} + def __init__(self, equations): + # 做一些初始化操作 + + def find(self, x): + # 根节点 + r = x + while r != parent[r]: + r = parent[r] + k = x + while k != r: + # 暂存parent[k]的父节点 + j = parent[k] + parent[k] = r + k = j + return r + def union(self, p, q): + if self.connected(p, q): return + self.parent[self.find(p)] = self.find(q) + def connected(self, p, q): + return self.find(p) == self.find(q) +``` diff --git a/todo/candidates/215.kth-largest-element-in-an-array.js b/todo/candidates/215.kth-largest-element-in-an-array.js new file mode 100644 index 0000000..358a152 --- /dev/null +++ b/todo/candidates/215.kth-largest-element-in-an-array.js @@ -0,0 +1,34 @@ +/* + * @lc app=leetcode id=215 lang=javascript + * + * [215] Kth Largest Element in an Array + */ +/** + * @param {number[]} nums + * @param {number} k + * @return {number} + */ +function maxHeapify(nums) { + nums.unshift(null); + + for (let i = nums.length - 1; i >> 1 > 0; i--) { + // 自下往上堆化 + if (nums[i] > nums[i >> 1]) { // 如果子元素更大,则交换位置 + const temp = nums[i]; + nums[i] = nums[i >> 1]; + nums[i >> 1] = temp; + } + } + nums.shift(); + return nums[0]; + } +var findKthLargest = function(nums, k) { + // heap klogn + let ret = null; + for(let i = 0; i < k; i++) { + ret = maxHeapify(nums); + nums.shift(); + } + return ret; +}; + diff --git a/todo/candidates/64.minimum-path-sum.js b/todo/candidates/64.minimum-path-sum.js new file mode 100644 index 0000000..722ea8a --- /dev/null +++ b/todo/candidates/64.minimum-path-sum.js @@ -0,0 +1,42 @@ +/* + * @lc app=leetcode id=64 lang=javascript + * + * [64] Minimum Path Sum + */ +/** + * @param {number[][]} grid + * @return {number} + */ +var minPathSum = function(grid) { +// 时间复杂度和空间复杂度都是 O (m * n); + if (grid.length === 0) return 0; + const dp = []; + const rows = grid.length; + const cols = grid[0].length; + // 实际上你也可以无差别全部填充为MAX_VALUE,对结果没影响,代码还会更少 + // 只是有点不专业而已 + for (let i = 0; i < rows + 1; i++) { + dp[i] = []; + // 初始化第一列 + dp[i][0] = Number.MAX_VALUE; + for (let j = 0; j < cols + 1; j++) { + // 初始化第一行 + if (i === 0) { + dp[i][j] = Number.MAX_VALUE; + } + } + } + + // tricky + dp[0][1] = 0; + + for (let i = 1; i < rows + 1; i++) { + for (let j = 1; j < cols + 1; j++) { + // state transition + dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1]; + } + } + + return dp[rows][cols]; +}; +