diff --git a/.editorconfig b/.editorconfig index d9c3abd..ee76204 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,29 +1,25 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# http://editorconfig.org -# 所有文件换行使用 Unix like 风格(LF),bat 文件使用 win 风格(CRLF) -# 缩进 java 4 个空格,其他所有文件 2 个空格 +# EditorConfig 用于在 IDE 中检查代码的基本 Code Style +# @see: https://editorconfig.org/ + +# 配置说明: +# 所有文件换行使用 Unix 风格(LF),*.bat 文件使用 Windows 风格(CRLF) +# java / sh 文件缩进 4 个空格,其他所有文件缩进 2 个空格 root = true [*] -# Unix-style newlines with a newline ending every file end_of_line = lf - -# Change these settings to your own preference indent_size = 2 indent_style = space max_line_length = 120 - -# We recommend you to keep these unchanged charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.bat] +[*.{bat, cmd}] end_of_line = crlf -[*.java] +[*.{java, gradle, groovy, kt, sh, xml}] indent_size = 4 [*.md] diff --git a/.gitattributes b/.gitattributes index e2a6e5e..eaae227 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,6 +22,7 @@ *.less text *.sql text *.properties text +*.md text # unix style *.sh text eol=lf @@ -50,26 +51,28 @@ *.bin binary *.exe binary -# 图片 +# images *.png binary *.jpg binary *.ico binary *.gif binary -# 音视频 +# medias *.mp3 binary *.swf binary -# 字体 +# fonts *.eot binary *.svg binary *.ttf binary *.woff binary -# other doc +# others *.pdf binary *.doc binary *.docx binary +*.ppt binary +*.pptx binary *.xls binary *.xlsx binary *.xmind binary diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..36b705c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,36 @@ +name: CI + +# 在master分支发生push事件时触发。 +on: + push: + branches: + - master + +env: # 设置环境变量 + TZ: Asia/Shanghai # 时区(设置时区可使页面中的`最近更新时间`使用时区时间) + +jobs: + build: # 自定义名称 + runs-on: ubuntu-latest # 运行在虚拟机环境ubuntu-latest + + strategy: + matrix: + node-version: [14.x] + + steps: + # 使用的动作。格式:userName/repoName。作用:检出仓库,获取源码。 官方actions库:https://github.com/actions + - name: Checkout + uses: actions/checkout@master + + # 指定 nodejs 版本 + - name: Use Nodejs ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + # 部署 + - name: Deploy + env: # 设置环境变量 + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} + GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }} + run: npm install && npm run deploy diff --git a/.gitignore b/.gitignore index 76e97fd..7d98dac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,37 @@ -################ JAVA ################ -# temp folders +# --------------------------------------------------------------------- +# more gitignore templates see https://github.com/github/gitignore +# --------------------------------------------------------------------- + +# ------------------------------- java ------------------------------- +# compiled folders classes target logs +.mtj.tmp/ -# temp files +# compiled files *.class + +# bluej files +*.ctxt + +# package files # *.jar *.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs +hs_err_pid* + +# maven plugin temp files +.flattened-pom.xml -################ JAVASCRIPT ################ +# ------------------------------- javascript ------------------------------- # dependencies node_modules @@ -19,6 +40,8 @@ build dist _book _jsdoc +.temp +.deploy*/ # temp files *.log @@ -26,13 +49,18 @@ npm-debug.log* yarn-debug.log* yarn-error.log* bundle*.js +.DS_Store +Thumbs.db +db.json +book.pdf +package-lock.json -################ IDEA ################ +# ------------------------------- intellij ------------------------------- .idea *.iml -################ Eclipse ################ +# ------------------------------- eclipse ------------------------------- .classpath .project diff --git a/LICENSE b/LICENSE index 8dada3e..3b7b82d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,427 @@ - 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 {yyyy} {name of copyright owner} - - 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. +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/README.md b/README.md index 3e8bb01..af71637 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,624 @@ -# Algorithm +

+ + logo + +

-> Java 实现算法 +

-## 笔记 + + star + -- [查找](docs/search/README.md) - - [线性表的查找](docs/search/linear-list-search.md) - - [哈希表的查找](docs/search/hash-search.md) -- [排序](docs/sort/README.md) - - [冒泡排序](docs/sort/bubble-sort.md) - - [快速排序](docs/sort/quick-sort.md) - - [直接插入排序](docs/sort/insert-sort.md) - - [希尔排序](docs/sort/shell-sort.md) - - [简单选择排序](docs/sort/selection-sort.md) - - [堆排序](docs/sort/heap-sort.md) - - [归并排序](docs/sort/merge-sort.md) - - [基数排序](docs/sort/radix-sort.md) + + fork + -## 源码 + + build + + + + code style + + +

+ +

ALGORITHM

+ +> 💾 algorithm 是一个数据结构与算法学习笔记。 +> +> 掌握数据结构与算法,你看待问题的深度,解决问题的角度就会完全不一样。 +> +> - 🔁 项目同步维护:[Github](https://github.com/dunwu/algorithm-tutorial/) | [Gitee](https://gitee.com/turnon/algorithm-tutorial/) +> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/algorithm-tutorial/) | [Gitee Pages](http://turnon.gitee.io/algorithm-tutorial/) + +## 📖 内容 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200702071922.png) + +- 综合 + - [数据结构和算法指南](docs/01.数据结构和算法/00.综合/01.数据结构和算法指南.md) + - [复杂度分析](docs/01.数据结构和算法/00.综合/02.复杂度分析.md) - 关键词:**`时间复杂度`**、**`空间复杂度`**、**`大 O 表示法`**、**`复杂度量级`** +- 线性表 + - [数组和链表](docs/01.数据结构和算法/01.线性表/01.数组和链表.md) - 关键词:**`线性表`**、**`一维数组`**、**`多维数组`**、**`随机访问`**、**`单链表`**、**`双链表`**、**`循环链表`** + - [栈和队列](docs/01.数据结构和算法/01.线性表/02.栈和队列.md) - 关键词:**`先进后出`**、**`后进先出`**、**`循环队列`** + - [线性表的查找](docs/01.数据结构和算法/01.线性表/11.线性表的查找.md) + - [线性表的排序](docs/01.数据结构和算法/01.线性表/12.线性表的排序.md) +- 树 + - [树和二叉树](docs/01.数据结构和算法/02.树/01.树和二叉树.md) + - [堆](docs/01.数据结构和算法/02.树/02.堆.md) + - [B+树](docs/01.数据结构和算法/02.树/03.B+树.md) + - [LSM 树](docs/01.数据结构和算法/02.树/04.LSM树.md) + - [字典树](docs/01.数据结构和算法/02.树/05.字典树.md) + - [红黑树](docs/01.数据结构和算法/02.树/06.红黑树.md) +- [哈希表](docs/01.数据结构和算法/03.哈希表.md) - 关键词:**`哈希函数`**、**`装载因子`**、**`哈希冲突`**、**`开放寻址法`**、**`拉链法`** +- [跳表](docs/01.数据结构和算法/04.跳表.md) - 关键词:**`多级索引`** +- [图](docs/01.数据结构和算法/05.图.md) + +## 💻 刷题 + +### 链表 + +#### 基础操作 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [1290. 二进制链表转整数](https://leetcode.cn/problems/convert-binary-number-in-a-linked-list-to-integer/) | 💚 | ✔️ | + +#### 双指针技巧 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------ | ---- | ------ | +| [141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/) | 💚 | ✔️ | +| [142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | 💛 | ✔️ | +| [160. 相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | 💚 | ✔️ | +| [19. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | 💛 | ✔️ | +| [21. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | 💚 | ✔️ | +| [23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | ❤️ | ✔️ | +| [86. 分隔链表](https://leetcode.cn/problems/partition-list/) | 💛 | ✔️ | +| [876. 链表的中间结点](https://leetcode.cn/problems/middle-of-the-linked-list/) | 💚 | ✔️ | +| [面试题 02. 返回倒数第 k 个节点](https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/) | 💚 | ✔️ | +| [面试题 02.01. 移除重复节点](https://leetcode.cn/problems/remove-duplicate-node-lcci/) | 💚 | ✔️ | +| [203. 移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/) | 💚 | ✔️ | +| [328. 奇偶链表](https://leetcode.cn/problems/odd-even-linked-list/) | 💛 | ✔️ | +| [LCR 136. 删除链表的节点](https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/) | 💚 | ✔️ | +| [83. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/) | 💚 | ✔️ | +| [82. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) | 💛 | ✔️ | +| [2. 两数相加](https://leetcode.cn/problems/add-two-numbers/) | 💛 | ✔️ | +| [445. 两数相加 II](https://leetcode.cn/problems/add-two-numbers-ii/) | 💛 | ✔️ | + +#### 单链表反转 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------ | ---- | ------ | +| [61. 旋转链表](https://leetcode.cn/problems/rotate-list/) | 💛 | ✔️ | +| [206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/) | 💚 | ✔️ | +| [92. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/) | 💛 | ✔️ | +| [25. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | ❤️ | ✔️ | + +#### 分治 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------- | ---- | ------ | +| [148. 排序链表](https://leetcode.cn/problems/sort-list/) | 💛 | ❌ | + +#### 回文链表 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------- | ---- | ------ | +| [234. 回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | 💚 | ✔️ | ### 数组 -- [寻找数组的中心索引](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/FindPivotIndex.java) -- [至少是其他数字两倍的最大数](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/LargestNumberAtLeastTwiceOfOthers.java) -- [加一](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PlusOne.java) -- [对角线遍历](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/DiagonalTraverse.java) -- [螺旋矩阵](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/SpiralMatrix.java) -- [杨辉三角](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PascalsTriangle.java) -- [杨辉三角 II](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PascalsTriangle2.java) -- [数组拆分 I](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/ArrayPartition.java) -- [两数之和 II - 输入有序数组](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/TwoSum2InputArrayIsSorted.java) -- [移除元素](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RemoveElement.java) -- [最大连续 1 的个数](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MaxConsecutiveOnes.java) -- [长度最小的子数组](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MinimumSizeSubarraySum.java) -- [旋转数组](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RotateArray.java) -- [删除排序数组中的重复项](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RemoveDuplicatesFromSortedArray.java) -- [移动零](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MoveZeros.java) - -### 字符串 - -- [二进制求和](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/AddBinary.java) -- [实现 strStr()](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ImplementStrstr.java) -- [最长公共前缀](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/LongestCommonPrefix.java) -- [反转字符串](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseString.java) -- [反转字符串中的单词](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString.java) -- [反转字符串中的单词 III ](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString3.java) +#### 基础 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [485. 最大连续 1 的个数](https://leetcode.cn/problems/max-consecutive-ones/) | 💚 | ✔️ | +| [747. 至少是其他数字两倍的最大数](https://leetcode.cn/problems/largest-number-at-least-twice-of-others/) | 💚 | ✔️ | + +#### 双指针技巧 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [27. 移除元素](https://leetcode.cn/problems/remove-element/) | 💚 | ✔️ | +| [283. 移动零](https://leetcode.cn/problems/move-zeroes/) | 💚 | ✔️ | +| [LCR 179. 查找总价格为目标值的两个商品](https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/) | 💚 | ✔️ | +| [1. 两数之和](https://leetcode.cn/problems/two-sum/) | 💚 | ✔️ | +| [67. 二进制求和](https://leetcode.cn/problems/add-binary/) | 💚 | ✔️ | +| [167. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/)
[LCR 006. 两数之和 II - 输入有序数组](https://leetcode.cn/problems/kLl5u1/) | 💛 | ✔️ | +| [26. 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) | 💚 | ✔️ | +| [80. 删除有序数组中的重复项 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/) | 💛 | ✔️ | +| [344. 反转字符串](https://leetcode.cn/problems/reverse-string/) | 💚 | ✔️ | +| [125. 验证回文串](https://leetcode.cn/problems/valid-palindrome/) | 💚 | ✔️ | +| [5. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | 💛 | ✔️ | +| [75. 颜色分类](https://leetcode.cn/problems/sort-colors/) | 💛 | ✔️ | +| [88. 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/) | 💚 | ✔️ | +| [977. 有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) | 💚 | ✔️ | +| [1329. 将矩阵按对角线排序](https://leetcode.cn/problems/sort-the-matrix-diagonally/) | 💛 | ✔️ | +| [1260. 二维网格迁移](https://leetcode.cn/problems/shift-2d-grid/) | 💚 | ✔️ | +| [867. 转置矩阵](https://leetcode.cn/problems/transpose-matrix/) | 💚 | ✔️ | +| [14. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix/) | 💚 | ✔️ | +| [15. 三数之和](https://leetcode.cn/problems/3sum/) | 💛 | ❗ | +| [56. 合并区间](https://leetcode.cn/problems/merge-intervals/) | 💛 | ✔️ | + +#### 二维数组遍历 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- | ------ | +| [151. 反转字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/) | 💛 | ✔️ | +| [48. 旋转图像](https://leetcode.cn/problems/rotate-image/) | 💛 | ✔️ | +| [54. 螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/)
[LCR 146. 螺旋遍历二维数组](https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) | 💛 | ✔️ | +| [59. 螺旋矩阵 II](https://leetcode.cn/problems/spiral-matrix-ii/) | 💛 | ✔️ | +| [498. 对角线遍历](https://leetcode.cn/problems/diagonal-traverse/) | 💛 | ❌ | +| [面试题 01.08. 零矩阵](https://leetcode.cn/problems/zero-matrix-lcci/) | 💛 | ✔️ | + +#### 滑动窗口算法 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [3. 无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | 💛 | ✔️ | +| [438. 找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | 💛 | ✔️ | +| [567. 字符串的排列](https://leetcode.cn/problems/permutation-in-string/) | 💛 | ✔️ | +| [76. 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | ❤️ | ✔️ | +| [1658. 将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/) | 💛 | ✔️ | +| [713. 乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/) | 💛 | ✔️ | +| [1004. 最大连续 1 的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/) | 💛 | ✔️ | +| [424. 替换后的最长重复字符](https://leetcode.cn/problems/longest-repeating-character-replacement/) | 💛 | ✔️ | +| [217. 存在重复元素](https://leetcode.cn/problems/contains-duplicate/) | 💚 | ✔️ | +| [219. 存在重复元素 II](https://leetcode.cn/problems/contains-duplicate-ii/) | 💛 | ✔️ | +| [220. 存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | ❤️ | ❌ | +| [209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | 💛 | ✔️ | +| [395. 至少有 K 个重复字符的最长子串](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/) | 💛 | ❌ | + +#### 二分查找算法 + +| 题目 | 难度 | 掌握度 | +| :-------------------------------------------------------------------------------------------------------------------------------------- | :--- | ------ | +| [34. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | 💛 | ✔️ | +| [35. 搜索插入位置](https://leetcode.cn/problems/search-insert-position/) | 💚 | ✔️ | +| [704. 二分查找](https://leetcode.cn/problems/binary-search/) | 💚 | ✔️ | +| [LCR 172. 统计目标成绩的出现次数](https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) | 💚 | ✔️ | +| [875. 爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) | 💛 | ❗ | +| [1011. 在 D 天内送达包裹的能力](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/) | 💛 | ✔️ | +| [410. 分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/) | ❤️ | ❌ | + +#### 前缀和数组 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [303. 区域和检索 - 数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/) | 💚 | ✔️ | +| [724. 寻找数组的中心下标](https://leetcode.cn/problems/find-pivot-index/) | 💚 | ✔️ | +| [1013. 将数组分成和相等的三个部分](https://leetcode.cn/problems/partition-array-into-three-parts-with-equal-sum/) | 💚 | ✔️ | +| [304. 二维区域和检索 - 矩阵不可变](https://leetcode.cn/problems/range-sum-query-2d-immutable/) | 💛 | ❌ | + +#### 差分数组 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------- | ---- | ------ | +| [1094. 拼车](https://leetcode.cn/problems/car-pooling/) | 💛 | ✔️ | +| [1109. 航班预订统计](https://leetcode.cn/problems/corporate-flight-bookings/) | 💛 | ✔️ | + +### 栈和队列 + +#### 队列 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------------------------------- | ---- | ------ | +| [225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) | 💚 | ✔️ | +| [933. 最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/) | 💚 | ❗ | +| [622. 设计循环队列](https://leetcode.cn/problems/design-circular-queue/) | 💛 | ❌ | +| [641. 设计循环双端队列](https://leetcode.cn/problems/design-circular-deque/) | 💛 | ❌ | +| [1670. 设计前中后队列](https://leetcode.cn/problems/design-front-middle-back-queue/) | 💛 | ❌ | +| [2073. 买票需要的时间](https://leetcode.cn/problems/time-needed-to-buy-tickets/) | 💚 | ✔️ | +| [373. 查找和最小的 K 对数字](https://leetcode.cn/problems/find-k-pairs-with-smallest-sums/) | 💛 | ❌ | +| [378. 有序矩阵中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/) | 💛 | ❌ | + +#### 栈 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------------- | ---- | ------ | +| [20. 有效的括号](https://leetcode.cn/problems/valid-parentheses/) | 💚 | ✔️ | +| [232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) | 💚 | ✔️ | +| [682. 棒球比赛](https://leetcode.cn/problems/baseball-game/) | 💚 | ✔️ | +| [844. 比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) | 💚 | ✔️ | +| [71. 简化路径](https://leetcode.cn/problems/simplify-path/) | 💛 | ✔️ | +| [143. 重排链表](https://leetcode.cn/problems/reorder-list/) | 💛 | ✔️ | +| [150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) | 💛 | ✔️ | +| [388. 文件的最长绝对路径](https://leetcode.cn/problems/longest-absolute-file-path/) | 💛 | ❌ | +| [155. 最小栈](https://leetcode.cn/problems/min-stack/) | 💛 | ✔️ | +| [面试题 03.05. 栈排序](https://leetcode.cn/problems/sort-of-stacks-lcci/) | 💛 | ✔️ | +| [895. 最大频率栈](https://leetcode.cn/problems/maximum-frequency-stack/) | ❤️ | ❌ | + +#### 单调栈 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [496. 下一个更大元素 I](https://leetcode.cn/problems/next-greater-element-i/) | 💚 | ✔️ | +| [503. 下一个更大元素 II](https://leetcode.cn/problems/next-greater-element-ii/) | 💛 | ✔️ | +| [739. 每日温度](https://leetcode.cn/problems/daily-temperatures/)
[剑指 Offer II 038. 每日温度](https://leetcode.cn/problems/iIQa4I/) | 💛 | ✔️ | +| [1019. 链表中的下一个更大节点](https://leetcode.cn/problems/next-greater-node-in-linked-list/) | 💛 | ✔️ | +| [1944. 队列中可以看到的人数](https://leetcode.cn/problems/number-of-visible-people-in-a-queue/) | ❤️ | ❌ | +| [1475. 商品折扣后的最终价格](https://leetcode.cn/problems/final-prices-with-a-special-discount-in-a-shop/) | 💛 | ✔️ | +| [901. 股票价格跨度](https://leetcode.cn/problems/online-stock-span/) | 💛 | ❌ | +| [402. 移掉 K 位数字](https://leetcode.cn/problems/remove-k-digits/) | 💛 | ❌ | +| [853. 车队](https://leetcode.cn/problems/car-fleet/) | 💛 | ❌ | +| [581. 最短无序连续子数组](https://leetcode.cn/problems/shortest-unsorted-continuous-subarray/) | 💛 | ❌ | + +#### 单调队列 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [LCR 184. 设计自助结算系统](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/) | 💛 | ❌ | +| [239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | ❤️ | ❌ | +| [1438. 绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | 💛 | ❌ | +| [862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) | ❤️ | ❌ | +| [918. 环形子数组的最大和](https://labuladong.online/algo/problem-set/monotonic-queue/#slug_maximum-sum-circular-subarray) | 💛 | ❌ | + +### 树 + +#### 二叉树 + +| 题目 | 难度 | 掌握度 | +| ---------------------------------------------------------------------------------------------------- | ---- | ------ | +| [104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | 💚 | ✔️ | +| [111. 二叉树的最小深度](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) | 💚 | ✔️ | +| [543. 二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | 💚 | ✔️ | +| [114. 二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/) | 💛 | ✔️ | +| [226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | 💚 | ✔️ | +| [654. 最大二叉树](https://leetcode.cn/problems/maximum-binary-tree/) | 💛 | ✔️ | +| [297. 二叉树的序列化与反序列化](https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/) | ❤️ | ❗ | +| [222. 完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes/) | 💚 | ✔️ | + +#### DFS + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------------------------------- | ---- | ------ | +| [144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) | 💚 | ✔️ | +| [94. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | 💚 | ✔️ | +| [145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) | 💚 | ✔️ | +| [872. 叶子相似的树](https://leetcode.cn/problems/leaf-similar-trees/) | 💚 | ✔️ | + +#### 用「遍历」思维解题 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------------------------------- | ---- | ------ | +| [257. 二叉树的所有路径](https://leetcode.cn/problems/binary-tree-paths/) | 💚 | ✔️ | +| [129. 求根节点到叶节点数字之和](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) | 💛 | ✔️ | +| [199. 二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | 💛 | ✔️ | +| [988. 从叶结点开始的最小字符串](https://leetcode.cn/problems/smallest-string-starting-from-leaf/) | 💛 | ✔️ | +| [1022. 从根到叶的二进制数之和](https://leetcode.cn/problems/sum-of-root-to-leaf-binary-numbers/) | 💚 | ✔️ | +| [1457. 二叉树中的伪回文路径](https://leetcode.cn/problems/pseudo-palindromic-paths-in-a-binary-tree/) | 💛 | ✔️ | +| [404. 左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/) | 💚 | ✔️ | +| [623. 在二叉树中增加一行](https://leetcode.cn/problems/add-one-row-to-tree/) | 💛 | ✔️ | +| [508. 出现次数最多的子树元素和](https://leetcode.cn/problems/most-frequent-subtree-sum/) | 💛 | ✔️ | +| [563. 二叉树的坡度](https://leetcode.cn/problems/binary-tree-tilt/) | 💚 | ✔️ | +| [814. 二叉树剪枝](https://leetcode.cn/problems/binary-tree-pruning/) | 💛 | ✔️ | +| [1325. 删除给定值的叶子节点](https://leetcode.cn/problems/delete-leaves-with-a-given-value/) | 💛 | ✔️ | + +#### 用「分解」思维解题 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | 💛 | ✔️ | +| [106. 从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) | 💛 | ✔️ | +| [889. 根据前序和后序遍历构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal/) | 💛 | ✔️ | +| [331. 验证二叉树的前序序列化](https://leetcode.cn/problems/verify-preorder-serialization-of-a-binary-tree/) | 💛 | ❌ | +| [894. 所有可能的真二叉树](https://leetcode.cn/problems/all-possible-full-binary-trees/) | 💛 | ❌ | +| [998. 最大二叉树 II](https://leetcode.cn/problems/maximum-binary-tree-ii/) | 💛 | ❌ | +| [1110. 删点成林](https://leetcode.cn/problems/delete-nodes-and-return-forest/) | 💛 | ❌ | +| [100. 相同的树](https://leetcode.cn/problems/same-tree/) | 💛 | ✔️ | +| [101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | 💛 | ✔️ | +| [951. 翻转等价二叉树](https://leetcode.cn/problems/flip-equivalent-binary-trees/) | 💛 | ✔️ | +| [124. 二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | ❤️ | ❌ | +| [236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | 💛 | ❌ | + +#### 用「层序遍历」思维解题 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------------------------ | ---- | ------ | +| [102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | 💛 | ✔️ | +| [107. 二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) | 💛 | ✔️ | +| [103. 二叉树的锯齿形层序遍历](https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/) | 💛 | ✔️ | +| [116. 填充每个节点的下一个右侧节点指针](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) | 💛 | ✔️ | +| [117. 填充每个节点的下一个右侧节点指针 II](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) | 💛 | ✔️ | +| [662. 二叉树最大宽度](https://leetcode.cn/problems/maximum-width-of-binary-tree/) | 💛 | ✔️ | +| [515. 在每个树行中找最大值](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) | 💛 | ✔️ | +| [637. 二叉树的层平均值](https://leetcode.cn/problems/average-of-levels-in-binary-tree/) | 💚 | ✔️ | +| [958. 二叉树的完全性检验](https://leetcode.cn/problems/check-completeness-of-a-binary-tree/) | 💛 | ✔️ | +| [1161. 最大层内元素和](https://leetcode.cn/problems/maximum-level-sum-of-a-binary-tree/) | 💛 | ✔️ | +| [1302. 层数最深叶子节点的和](https://leetcode.cn/problems/deepest-leaves-sum/) | 💛 | ✔️ | +| [1609. 奇偶树](https://leetcode.cn/problems/even-odd-tree/) | 💛 | ✔️ | +| [919. 完全二叉树插入器](https://leetcode.cn/problems/complete-binary-tree-inserter/) | 💛 | ✔️ | +| [863. 二叉树中所有距离为 K 的结点](https://leetcode.cn/problems/all-nodes-distance-k-in-binary-tree/) | 💛 | ❌ | +| [LCR 149. 彩灯装饰记录 I](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/) | 💛 | ✔️ | +| [LCR 150. 彩灯装饰记录 II](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/) | 💚 | ✔️ | +| [LCR 151. 彩灯装饰记录 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/) | 💛 | ✔️ | + +#### 二叉搜索树 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [1038. 从二叉搜索树到更大和树](https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/) | 💛 | ✔️ | +| [230. 二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | 💛 | ✔️ | +| [538. 把二叉搜索树转换为累加树](https://leetcode.cn/problems/convert-bst-to-greater-tree/) | 💛 | ✔️ | +| [450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/) | 💛 | ✔️ | +| [700. 二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/) | 💚 | ✔️ | +| [701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) | 💛 | ✔️ | +| [98. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | 💛 | ✔️ | +| [96. 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/) | 💛 | ❌ | +| [95. 不同的二叉搜索树 II](https://leetcode.cn/problems/unique-binary-search-trees-ii/) | 💛 | ❌ | +| [108. 将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | 💚 | ✔️ | +| [783. 二叉搜索树节点最小距离](https://leetcode.cn/problems/minimum-distance-between-bst-nodes/) | 💚 | ✔️ | +| [235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) | 💛 | ❌ | +| [1373. 二叉搜索子树的最大键值和](https://leetcode.cn/problems/maximum-sum-bst-in-binary-tree/) | ❤️ | ❌ | + +#### N 叉树 + +| 题目 | 难度 | 掌握度 | +| :-------------------------------------------------------------------------------------- | :--: | ------ | +| [429. N 叉树的层序遍历](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/) | 💛 | ✔️ | +| [559. N 叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-n-ary-tree/) | 💚 | ✔️ | +| [589. N 叉树的前序遍历](https://leetcode.cn/problems/n-ary-tree-preorder-traversal/) | 💚 | ✔️ | +| [590. N 叉树的后序遍历](https://leetcode.cn/problems/n-ary-tree-postorder-traversal/) | 💚 | ✔️ | + +### 图 + +#### BFS/DFS + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------ | ---- | ------ | +| [797. 所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/) | 💛 | ❗ | + +#### 环检测及拓扑排序算法 + +| 题目 | 难度 | 掌握度 | +| :----------------------------------------------------------------- | ---- | ------ | +| [207. 课程表](https://leetcode.cn/problems/course-schedule/) | 💛 | ❌ | +| [210. 课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | 💛 | ❌ | + +#### 二分图判定算法 + +| 题目 | 难度 | 掌握度 | +| :---------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [785. 判断二分图](https://leetcode.cn/problems/is-graph-bipartite/)
[LCR 106. 判断二分图](https://leetcode.cn/problems/vEAB3K/) | 💛 | ❌ | +| [886. 可能的二分法](https://leetcode.cn/problems/possible-bipartition/) | 💛 | ❗ | + +#### 并查集算法 + +| 题目 | 难度 | 掌握度 | +| :-------------------------------------------------------------------------------------------- | ---- | ------ | +| [130. 被围绕的区域](https://leetcode.cn/problems/surrounded-regions/) | 💛 | ❌ | +| [684. 冗余连接](https://leetcode.cn/problems/redundant-connection/) | 💛 | ✔️ | +| [990. 等式方程的可满足性](https://leetcode.cn/problems/satisfiability-of-equality-equations/) | 💛 | ✔️ | + +#### Dijkstra 算法 + +| 题目 | 难度 | 掌握度 | +| :--------------------------------------------------------------------------------------------------------------------------------- | ---- | ------ | +| [743. 网络延迟时间](https://leetcode.cn/problems/network-delay-time/) | 💛 | ❌ | +| [1631. 最小体力消耗路径](https://leetcode.cn/problems/path-with-minimum-effort/) | 💛 | ❌ | +| [1514. 概率最大的路径](https://leetcode.cn/problems/path-with-maximum-probability/) | 💛 | ❌ | +| [787. K 站中转内最便宜的航班](https://leetcode.cn/problems/cheapest-flights-within-k-stops/) | 💛 | ❌ | +| [1368. 使网格图至少有一条有效路径的最小代价](https://leetcode.cn/problems/minimum-cost-to-make-at-least-one-valid-path-in-a-grid/) | ❤️ | ❌ | + +### DFS / 回溯算法 + +#### 排列、组合、子集问题 + +子集、组合、排列相关问题,都可以考虑使用回溯算法求解。 + +| 题目 | 难度 | 掌握度 | +| :--------------------------------------------------------------------- | ---- | ------ | +| [46. 全排列](https://leetcode.cn/problems/permutations/) | 💛 | ✔️ | +| [47. 全排列 II](https://leetcode.cn/problems/permutations-ii/) | 💛 | ✔️ | +| [78. 子集](https://leetcode.cn/problems/subsets/) | 💛 | ✔️ | +| [90. 子集 II](https://leetcode.cn/problems/subsets-ii/) | 💛 | ✔️ | +| [77. 组合](https://leetcode.cn/problems/combinations/) | 💛 | ✔️ | +| [39. 组合总和](https://leetcode.cn/problems/combination-sum/) | 💛 | ✔️ | +| [40. 组合总和 II](https://leetcode.cn/problems/combination-sum-ii/) | 💛 | ✔️ | +| [216. 组合总和 III](https://leetcode.cn/problems/combination-sum-iii/) | 💛 | ✔️ | + +#### 岛屿问题 + +| 题目 | 难度 | 掌握度 | +| :--------------------------------------------------------------------------------- | ---- | ------ | +| [200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/) | 💛 | ❗ | +| [1254. 统计封闭岛屿的数目](https://leetcode.cn/problems/number-of-closed-islands/) | 💛 | ❗ | +| [1020. 飞地的数量](https://leetcode.cn/problems/number-of-enclaves/) | 💛 | ❗ | +| [695. 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/) | 💛 | ❗ | +| [1905. 统计子岛屿](https://leetcode.cn/problems/count-sub-islands/) | 💛 | ❌ | + +#### 数独、N 皇后问题 + +| 题目 | 难度 | 掌握度 | +| :-------------------------------------------------------- | ---- | ------ | +| [37. 解数独](https://leetcode.cn/problems/sudoku-solver/) | ❤️ | ❌ | +| [51. N 皇后](https://leetcode.cn/problems/n-queens/) | ❤️ | ❌ | +| [52. N皇后 II](https://leetcode.cn/problems/n-queens-ii/) | ❤️ | ❌ | + +#### 练习 + +| 题目 | 难度 | 掌握度 | +| :----------------------------------------------------------------------------------------------- | ---- | ------ | +| [967. 连续差相同的数字](https://leetcode.cn/problems/numbers-with-same-consecutive-differences/) | 💛 | ❌ | +| [491. 非递减子序列](https://leetcode.cn/problems/non-decreasing-subsequences/) | 💛 | ❌ | +| [980. 不同路径 III](https://leetcode.cn/problems/unique-paths-iii/) | ❤️ | ❌ | +| [526. 优美的排列](https://leetcode.cn/problems/beautiful-arrangement/) | 💛 | ❌ | +| [131. 分割回文串](https://leetcode.cn/problems/palindrome-partitioning/) | 💛 | ❌ | +| [93. 复原 IP 地址](https://leetcode.cn/problems/restore-ip-addresses/) | 💛 | ❌ | +| [89. 格雷编码](https://leetcode.cn/problems/gray-code/) | 💛 | ❌ | +| [17. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) | 💛 | ❌ | +| [79. 单词搜索](https://leetcode.cn/problems/word-search/) | 💛 | ❌ | + +### BFS + +| 题目 | 难度 | 掌握度 | +| :----------------------------------------------------------------------------------------------- | :--: | ------ | +| [752. 打开转盘锁](https://leetcode.cn/problems/open-the-lock/) | 💛 | ❌ | +| [773. 滑动谜题](https://leetcode.cn/problems/sliding-puzzle/) | ❤️ | ❌ | +| [919. 完全二叉树插入器](https://leetcode.cn/problems/complete-binary-tree-inserter/) | 💛 | ✔️ | +| [841. 钥匙和房间](https://leetcode.cn/problems/keys-and-rooms/) | 💛 | ✔️ | +| [433. 最小基因变化](https://leetcode.cn/problems/minimum-genetic-mutation/) | 💛 | ❗ | +| [1926. 迷宫中离入口最近的出口](https://leetcode.cn/problems/nearest-exit-from-entrance-in-maze/) | 💛 | ✔️ | +| [1091. 二进制矩阵中的最短路径](https://leetcode.cn/problems/shortest-path-in-binary-matrix/) | 💛 | ✔️ | +| [994. 腐烂的橘子](https://leetcode.cn/problems/rotting-oranges/) | 💛 | ✔️ | +| [365. 水壶问题](https://leetcode.cn/problems/water-and-jug-problem/) | 💛 | ❌ | +| [721. 账户合并](https://leetcode.cn/problems/accounts-merge/) | 💛 | ❌ | +| [127. 单词接龙](https://leetcode.cn/problems/word-ladder/) | ❤️ | ❌ | + +### 动态规划 + +#### 斐波那契 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------- | :--: | :----: | +| [509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | 💚 | ✔️ | +| [1137. 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/) | 💚 | ✔️ | +| [70. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | 💚 | ✔️ | +| [746. 使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing-stairs/) | 💚 | ✔️ | +| [198. 打家劫舍](https://leetcode.cn/problems/house-robber/) | 💛 | ✔️ | +| [740. 删除并获得点数](https://leetcode.cn/problems/delete-and-earn/) | 💛 | ✔️ | + +#### 一维 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------------------------ | :--: | :----: | +| [2140. 解决智力问题](https://leetcode.cn/problems/solving-questions-with-brainpower/) | 💛 | ❌ | +| [2466. 统计构造好字符串的方案数](https://leetcode.cn/problems/count-ways-to-build-good-strings/) | 💛 | ❌ | +| [91. 解码方法](https://leetcode.cn/problems/decode-ways/) | 💛 | ❌ | +| [983. 最低票价](https://leetcode.cn/problems/minimum-cost-for-tickets/) | 💛 | ❌ | +| [264. 丑数 II](https://leetcode.cn/problems/ugly-number-ii/) | 💛 | ❗ | +| [1201. 丑数 III](https://leetcode.cn/problems/ugly-number-iii/) | 💛 | ❌ | +| [313. 超级丑数](https://leetcode.cn/problems/super-ugly-number/) | 💛 | ❌ | + +#### 矩阵 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------- | :--: | :----: | +| [118. 杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | 💚 | ✔️ | +| [119. 杨辉三角 II](https://leetcode.cn/problems/pascals-triangle-ii/) | 💚 | ✔️ | +| [62. 不同路径](https://leetcode.cn/problems/unique-paths/) | 💛 | ✔️ | +| [63. 不同路径 II](https://leetcode.cn/problems/unique-paths-ii/) | 💛 | ✔️ | +| [64. 最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | 💛 | ✔️ | +| [120. 三角形最小路径和](https://leetcode.cn/problems/triangle/) | 💛 | ✔️ | +| [931. 下降路径最小和](https://leetcode.cn/problems/minimum-falling-path-sum/) | 💛 | ✔️ | +| [221. 最大正方形](https://leetcode.cn/problems/maximal-square/) | 💛 | ✔️ | + +#### 字符串 + +| 题目 | 难度 | 掌握度 | +| ---------------------------------------------------------------------------------------------------------- | :--: | :----: | +| [5. 最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | 💛 | ✔️ | +| [139. 单词拆分](https://leetcode.cn/problems/word-break/) | 💛 | ❌ | +| [72. 编辑距离](https://leetcode.cn/problems/edit-distance/) | 💛 | ❗ | +| [583. 两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) | 💛 | ❌ | +| [712. 两个字符串的最小ASCII删除和](https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/) | 💛 | ❌ | +| [516. 最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | 💛 | ❌ | +| [115. 不同的子序列](https://leetcode.cn/problems/distinct-subsequences/) | ❤️ | ❌ | + +#### 最长递增/公共子序列 + +| 题目 | 难度 | 掌握度 | +| --------------------------------------------------------------------------------------------------------------------------- | :--: | :----: | +| [300. 最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | 💛 | ❌ | +| [673. 最长递增子序列的个数](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) | 💛 | ❌ | +| [646. 最长数对链](https://leetcode.cn/problems/maximum-length-of-pair-chain/) | 💛 | ✔️ | +| [1218. 最长定差子序列](https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-difference/) | 💛 | ❌ | +| [1027. 最长等差数列](https://leetcode.cn/problems/longest-arithmetic-subsequence/) | 💛 | ❌ | +| [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | 💛 | ❗ | +| [1035. 不相交的线](https://leetcode.cn/problems/uncrossed-lines/) | 💛 | ❌ | +| [1312. 让字符串成为回文串的最少插入次数](https://leetcode.cn/problems/minimum-insertion-steps-to-make-a-string-palindrome/) | ❤️ | ❌ | + +#### 背包问题 + +| 题目 | 难度 | 掌握度 | +| ----------------------------------------------------------------------------- | ---- | ------ | +| [416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) | 💛 | ❌ | +| [322. 零钱兑换](https://leetcode.cn/problems/coin-change/) | 💛 | ❌ | +| [518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | 💛 | ❌ | + +#### 买卖股票的最佳时间/状态机 + +#### 其他 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------------------------------- | ---- | ------ | +| [53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | 💛 | ❌ | +| [354. 俄罗斯套娃信封问题](https://leetcode.cn/problems/russian-doll-envelopes/) | ❤️ | ❌ | + +### 贪心算法 + +| 题目 | 难度 | 掌握度 | +| -------------------------------------------------------------- | ---- | ------ | +| [561. 数组拆分](https://leetcode.cn/problems/array-partition/) | 💚 | ❌ | +| [55. 跳跃游戏](https://leetcode.cn/problems/jump-game/) | 💛 | ❌ | +| [45. 跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | 💛 | ❌ | + +### 分治算法 + +| 题目 | 掌握度 | +| --------------------------------------------------------------------------- | ------ | +| [23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | ✔️ | + +### 数学 + +| 题目 | 难度 | 掌握度 | +| ------------------------------------------------------ | ---- | ------ | +| [66. 加一](https://leetcode.cn/problems/plus-one/) | 💚 | ✔️ | +| [263. 丑数](https://leetcode.cn/problems/ugly-number/) | 💚 | ✔️ | + +## 📚 资料 + +- **书籍** + - **刷题必备** + - 《剑指 offer》 + - 《编程之美》 + - 《编程之法:面试和算法心得》 + - 《算法谜题》 都是思维题 + - **基础** + - [《编程珠玑(第 2 版)》](https://www.amazon.cn/gp/product/B00SFZH0DC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00SFZH0DC&linkCode=as2&tag=vastwork-23) + - [《编程珠玑(续)》](https://www.amazon.cn/gp/product/B0150BMQDM/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B0150BMQDM&linkCode=as2&tag=vastwork-23) + - [《数据结构与算法分析 : C++描述(第 4 版)》](https://www.amazon.cn/gp/product/B01LDG2DSG/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01LDG2DSG&linkCode=as2&tag=vastwork-23) + - [《数据结构与算法分析 : C 语言描述(第 2 版)》](https://www.amazon.cn/gp/product/B002WC7NGS/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B002WC7NGS&linkCode=as2&tag=vastwork-23) + - [《数据结构与算法分析 : Java 语言描述(第 2 版)》](https://www.amazon.cn/gp/product/B01CNP0CG6/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01CNP0CG6&linkCode=as2&tag=vastwork-23) + - [《算法(第 4 版)》](https://www.amazon.cn/gp/product/B009OCFQ0O/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B009OCFQ0O&linkCode=as2&tag=vastwork-23) + - **算法设计** + - [《算法设计与分析基础(第 3 版)》](https://www.amazon.cn/gp/product/B00S4HCQUI/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00S4HCQUI&linkCode=as2&tag=vastwork-23) + - 《Algorithm Design Manual》 - 算法设计手册 红皮书 + - [《算法导论》](https://www.amazon.cn/gp/product/B00AK7BYJY/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00AK7BYJY&linkCode=as2&tag=vastwork-23) - 是一本对算法介绍比较全面的经典书籍 + - 《Algorithms on Strings,Trees and Sequences》 + - 《Advanced Data Structures》 - 各种诡异高级的数据结构和算法 如元胞自动机、斐波纳契堆、线段树 600 块 +- **学习网站** + - https://labuladong.online/algo/ + - https://github.com/TheAlgorithms/Java + - https://github.com/nonstriater/Learn-Algorithms + - https://github.com/trekhleb/javascript-algorithms + - https://github.com/wangzheng0822/algo + - https://github.com/kdn251/interviews/blob/master/README-zh-cn.md#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84 + - [July 博客](http://blog.csdn.net/v_july_v) + - 《数学建模十大经典算法》 + - 《数据挖掘领域十大经典算法》 + - 《十道海量数据处理面试题》 + - 《数字图像处理领域的二十四个经典算法》 + - 《精选微软等公司经典的算法面试 100 题》 + - [The-Art-Of-Programming-By-July](https://github.com/julycoding/The-Art-Of-Programming-By-July) + - [微软面试 100 题](http://blog.csdn.net/column/details/ms100.html) + - [程序员编程艺术](http://blog.csdn.net/v_JULY_v/article/details/6460494) +- **基本算法演示** + - + - +- **编程网站** + - [leetcode](http://leetcode-cn.com/) + - [openjudge](http://openjudge.cn/) +- **教程** + - [高级数据结构和算法](https://www.coursera.org/learn/gaoji-shuju-jiegou/) 北大教授张铭老师在 coursera 上的课程。完成这门课之时,你将掌握多维数组、广义表、Trie 树、AVL 树、伸展树等高级数据结构,并结合内排序、外排序、检索、索引有关的算法,高效地解决现实生活中一些比较复杂的应用问题。当然 coursera 上也还有很多其它算法方面的视频课程。 + - [算法设计与分析 Design and Analysis of Algorithms](https://class.coursera.org/algorithms-001/lecture) 由北大教授 Wanling Qu 在 coursera 讲授的一门算法课程。首先介绍一些与算法有关的基础知识,然后阐述经典的算法设计思想和分析技术,主要涉及的算法设计技术是:分治策略、动态规划、贪心法、回溯与分支限界等。每个视频都配有相应的讲义(pdf 文件)以便阅读和复习。 + - [算法面试通关 40 讲](https://time.geekbang.org/course/intro/100019701) + - [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) + - [Data Structures - Computer Science Course for Beginners](https://www.youtube.com/watch?v=zg9ih6SVACc) - 高赞 YouTube 视频教程 + +## 🚪 传送 + +| [技术文档归档](https://github.com/dunwu/blog) | [算法和数据结构教程系列](https://github.com/dunwu/algorithm-tutorial) | diff --git a/codes/algorithm/pom.xml b/codes/algorithm/pom.xml index 8ef8793..307f4bd 100644 --- a/codes/algorithm/pom.xml +++ b/codes/algorithm/pom.xml @@ -1,15 +1,14 @@ - + 4.0.0 - io.github.dunwu - algorithm-demos - 1.0.1 + io.github.dunwu.algorithm + algorithm + 1.0.0 jar - Algorithm Demos - 算法 + 算法示例 + 数据示例源码 UTF-8 @@ -19,16 +18,43 @@ + + cn.hutool + hutool-all + 5.8.29 + + + org.projectlombok + lombok + 1.18.36 + provided + ch.qos.logback logback-classic - 1.1.3 + 1.2.9 - junit - junit - 4.12 - test + org.junit.jupiter + junit-jupiter + 5.8.2 + + + org.assertj + assertj-core + 3.26.3 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + none + + + + diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/TwoSum.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/TwoSum.java deleted file mode 100644 index 2abb8ac..0000000 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/TwoSum.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.dunwu.ds.array; - -import java.util.HashMap; -import java.util.Map; - -//Given an array of integers, return indices of the two nums 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]. -public class TwoSum { - public static int[] twoSum(int[] nums, int target) { - int[] result = new int[2]; - Map map = new HashMap(); - for (int i = 0; i < nums.length; i++) { - if (map.containsKey(target - nums[i])) { - result[1] = i; - result[0] = map.get(target - nums[i]); - return result; - } - map.put(nums[i], i); - } - return result; - } - - public static void main(String[] args) { - int[] nums = new int[]{2, 7, 11, 15}; - int target = 18; - int[] result = twoSum(nums, target); - System.out.println(result); - } -} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" new file mode 100644 index 0000000..7e1cb56 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.array.base; + +import org.junit.jupiter.api.Assertions; + +/** + * 485. 最大连续 1 的个数 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 最大连续1的个数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.findMaxConsecutiveOnes(new int[] { 1, 1, 0, 1, 1, 1 })); + Assertions.assertEquals(2, s.findMaxConsecutiveOnes(new int[] { 1, 0, 1, 1, 0, 1 })); + } + + static class Solution { + + public int findMaxConsecutiveOnes(int[] nums) { + int max = 0; + int cnt = 0; + for (int num : nums) { + if (num == 1) { + cnt++; + max = Math.max(max, cnt); + } else { + cnt = 0; + } + } + return max; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" new file mode 100644 index 0000000..cd5ceb9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/base/\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.array.base; + +import org.junit.jupiter.api.Assertions; + +/** + * 747. 至少是其他数字两倍的最大数 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 至少是其他数字两倍的最大数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.dominantIndex(new int[] { 3, 6, 1, 0 })); + Assertions.assertEquals(-1, s.dominantIndex(new int[] { 1, 2, 3, 4 })); + Assertions.assertEquals(0, s.dominantIndex(new int[] { 1, 0 })); + } + + static class Solution { + + public int dominantIndex(int[] nums) { + int second = -1, max = 0; + for (int i = 1; i < nums.length; i++) { + if (nums[i] > nums[max]) { + second = max; + max = i; + } else if (second == -1 || nums[i] > nums[second]) { + second = i; + } + } + return nums[max] >= 2 * nums[second] ? max : -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\344\272\214\345\210\206\346\237\245\346\211\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\344\272\214\345\210\206\346\237\245\346\211\276.java" new file mode 100644 index 0000000..1a47983 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\344\272\214\345\210\206\346\237\245\346\211\276.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 704. 二分查找 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 二分查找 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.search(new int[] { -1, 0, 3, 5, 9, 12 }, 9)); + Assertions.assertEquals(-1, s.search(new int[] { -1, 0, 3, 5, 9, 12 }, 2)); + } + + static class Solution { + + public int search(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] == target) { + return mid; + } else if (nums[mid] < target) { + left = mid + 1; + } else { + right = mid - 1; + } + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.java" new file mode 100644 index 0000000..0c3678b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\210\206\345\211\262\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\200\274.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 410. 分割数组的最大值 + * + * @author Zhang Peng + * @date 2025-10-16 + */ +public class 分割数组的最大值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(18, s.splitArray(new int[] { 7, 2, 5, 10, 8 }, 2)); + Assertions.assertEquals(9, s.splitArray(new int[] { 1, 2, 3, 4, 5 }, 2)); + Assertions.assertEquals(4, s.splitArray(new int[] { 1, 4, 4 }, 3)); + } + + static class Solution { + + public int splitArray(int[] nums, int k) { + return shipWithinDays(nums, k); + } + + public int shipWithinDays(int[] weights, int days) { + int max = 0, sum = 0; + for (int weight : weights) { + max = Math.max(max, weight); + sum += weight; + } + + int left = max, right = sum; + while (left <= right) { + int mid = left + (right - left) / 2; + if (f(weights, mid) <= days) { + // 需要让 f(x) 的返回值大一些 + right = mid - 1; + } else if (f(weights, mid) > days) { + // 需要让 f(x) 的返回值小一些 + left = mid + 1; + } + } + return left; + } + + // 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物 + // f(x) 随着 x 的增加单调递减 + int f(int[] weights, int x) { + int days = 0; + for (int i = 0; i < weights.length; ) { + // 尽可能多装货物 + int cap = x; + while (i < weights.length) { + if (cap < weights[i]) break; + else cap -= weights[i]; + i++; + } + days++; + } + return days; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250D\345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250D\345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.java" new file mode 100644 index 0000000..04d948d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250D\345\244\251\345\206\205\351\200\201\350\276\276\345\214\205\350\243\271\347\232\204\350\203\275\345\212\233.java" @@ -0,0 +1,62 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 1011. 在 D 天内送达包裹的能力 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 在D天内送达包裹的能力 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(15, s.shipWithinDays(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 5)); + Assertions.assertEquals(6, s.shipWithinDays(new int[] { 3, 2, 2, 4, 1, 4 }, 3)); + Assertions.assertEquals(3, s.shipWithinDays(new int[] { 1, 2, 3, 1, 1 }, 4)); + } + + static class Solution { + + public int shipWithinDays(int[] weights, int days) { + int max = 0, sum = 0; + for (int weight : weights) { + max = Math.max(max, weight); + sum += weight; + } + + int left = max, right = sum; + while (left <= right) { + int mid = left + (right - left) / 2; + if (f(weights, mid) <= days) { + // 需要让 f(x) 的返回值大一些 + right = mid - 1; + } else if (f(weights, mid) > days) { + // 需要让 f(x) 的返回值小一些 + left = mid + 1; + } + } + return left; + } + + // 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物 + // f(x) 随着 x 的增加单调递减 + int f(int[] weights, int x) { + int days = 0; + for (int i = 0; i < weights.length; ) { + // 尽可能多装货物 + int cap = x; + while (i < weights.length) { + if (cap < weights[i]) break; + else cap -= weights[i]; + i++; + } + days++; + } + return days; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" new file mode 100644 index 0000000..fb14865 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256.java" @@ -0,0 +1,79 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 34.在排序数组中查找元素的第一个和最后一个位置 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 在排序数组中查找元素的第一个和最后一个位置 { + + public static void main(String[] args) { + Solution s = new Solution(); + + Assertions.assertEquals(-1, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 3)); + Assertions.assertEquals(0, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 5)); + Assertions.assertEquals(5, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 10)); + Assertions.assertEquals(-1, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 12)); + Assertions.assertEquals(1, s.searchLeft(new int[] { 5, 7, 7, 8, 8, 10 }, 7)); + + Assertions.assertEquals(-1, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 3)); + Assertions.assertEquals(0, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 5)); + Assertions.assertEquals(5, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 10)); + Assertions.assertEquals(-1, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 12)); + Assertions.assertEquals(2, s.searchRight(new int[] { 5, 7, 7, 8, 8, 10 }, 7)); + + Assertions.assertArrayEquals(new int[] { 3, 4 }, s.searchRange(new int[] { 5, 7, 7, 8, 8, 10 }, 8)); + Assertions.assertArrayEquals(new int[] { -1, -1 }, s.searchRange(new int[] { 5, 7, 7, 8, 8, 10 }, 6)); + Assertions.assertArrayEquals(new int[] { -1, -1 }, s.searchRange(new int[] {}, 0)); + Assertions.assertArrayEquals(new int[] { 0, 0 }, s.searchRange(new int[] { 1 }, 1)); + } + + static class Solution { + + public int[] searchRange(int[] nums, int target) { + int left = searchLeft(nums, target); + int right = searchRight(nums, target); + return new int[] { left, right }; + } + + public int searchLeft(int[] nums, int target) { + int res = -1; + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + res = mid; + right = mid - 1; + } + } + return res; + } + + public int searchRight(int[] nums, int target) { + int res = -1; + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] == target) { + res = mid; + left = mid + 1; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" new file mode 100644 index 0000000..bdc2bb9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 35. 搜索插入位置 + * + * @author Zhang Peng + * @since 2020-07-29 + */ +public class 搜索插入位置 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(0, s.searchInsert(new int[] { 1 }, 1)); + Assertions.assertEquals(2, s.searchInsert(new int[] { 1, 3, 5, 6 }, 5)); + Assertions.assertEquals(1, s.searchInsert(new int[] { 1, 3, 5, 6 }, 2)); + Assertions.assertEquals(4, s.searchInsert(new int[] { 1, 3, 5, 6 }, 7)); + Assertions.assertEquals(0, s.searchInsert(new int[] { 1, 3, 5, 6 }, 0)); + } + + static class Solution { + + public int searchInsert(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] == target) { + return mid; + } else if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } + } + return left; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.java" new file mode 100644 index 0000000..c7b5f17 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\210\261\345\220\203\351\246\231\350\225\211\347\232\204\347\217\202\347\217\202.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * 875. 爱吃香蕉的珂珂 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 爱吃香蕉的珂珂 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.minEatingSpeed(new int[] { 3, 6, 7, 11 }, 8)); + Assertions.assertEquals(30, s.minEatingSpeed(new int[] { 30, 11, 23, 4, 20 }, 5)); + Assertions.assertEquals(23, s.minEatingSpeed(new int[] { 30, 11, 23, 4, 20 }, 6)); + Assertions.assertEquals(2, s.minEatingSpeed(new int[] { 312884470 }, 312884469)); + Assertions.assertEquals(3, s.minEatingSpeed(new int[] { 805306368, 805306368, 805306368 }, 1000000000)); + Assertions.assertEquals(14, s.minEatingSpeed( + new int[] { 332484035, 524908576, 855865114, 632922376, 222257295, 690155293, 112677673, 679580077, + 337406589, 290818316, 877337160, 901728858, 679284947, 688210097, 692137887, 718203285, 629455728, + 941802184 }, 823855818)); + } + + static class Solution { + + public int minEatingSpeed(int[] piles, int h) { + int left = 1, right = 1_000_000_000; + + // right 是闭区间,所以这里改成 <= + while (left <= right) { + int mid = left + (right - left) / 2; + if (f(piles, mid) <= h) { + // right 是闭区间,所以这里用 mid - 1 + right = mid - 1; + } else if (f(piles, mid) > h) { + left = mid + 1; + } + } + return left; + } + + long f(int[] nums, int x) { + long h = 0; + for (int num : nums) { + h += num / x; + if (num % x > 0) { h++; } + } + return h; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\273\237\350\256\241\347\233\256\346\240\207\346\210\220\347\273\251\347\232\204\345\207\272\347\216\260\346\254\241\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\273\237\350\256\241\347\233\256\346\240\207\346\210\220\347\273\251\347\232\204\345\207\272\347\216\260\346\254\241\346\225\260.java" new file mode 100644 index 0000000..c0266ae --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/bsearch/\347\273\237\350\256\241\347\233\256\346\240\207\346\210\220\347\273\251\347\232\204\345\207\272\347\216\260\346\254\241\346\225\260.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.array.bsearch; + +import org.junit.jupiter.api.Assertions; + +/** + * LCR 172. 统计目标成绩的出现次数 + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 统计目标成绩的出现次数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.countTarget(new int[] { 2, 2, 3, 4, 4, 4, 5, 6, 6, 8 }, 4)); + Assertions.assertEquals(0, s.countTarget(new int[] { 1, 2, 3, 5, 7, 9 }, 6)); + } + + static class Solution { + + public int countTarget(int[] scores, int target) { + int leftBound = searchLeft(scores, target); + if (leftBound == -1) { return 0; } + int cnt = 1; + for (int i = leftBound + 1; i < scores.length; i++) { + if (scores[i] == target) { + cnt++; + } + } + return cnt; + } + + public int searchLeft(int[] nums, int target) { + int res = -1; + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + right = mid - 1; + res = mid; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList1.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList1.java" new file mode 100644 index 0000000..1787652 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/demo/\346\250\241\346\213\237ArrayList1.java" @@ -0,0 +1,104 @@ +package io.github.dunwu.algorithm.array.demo; + +import java.util.Arrays; + +/** + * 1) 数组的插入、删除、按照下标随机访问操作; 2)数组中的数据是int类型的; + *

+ * Author: Zheng modify: xing, Gsealy + */ +public class 模拟ArrayList1 { + + //定义整型数据data保存数据 + public int data[]; + //定义数组长度 + private int n; + //定义中实际个数 + private int count; + + //构造方法,定义数组大小 + public 模拟ArrayList1(int capacity) { + this.data = new int[capacity]; + this.n = capacity; + this.count = 0;//一开始一个数都没有存所以为0 + } + + //根据索引,找到数据中的元素并返回 + public int find(int index) { + if (index < 0 || index >= count) return -1; + return data[index]; + } + + //插入元素:头部插入,尾部插入 + public boolean insert(int index, int value) { + //数组中无元素 + + //if (index == count && count == 0) { + // data[index] = value; + // ++count; + // return true; + //} + + // 数组空间已满 + if (count == n) { + System.out.println("动态扩容"); + data = Arrays.copyOf(data, n << 1); + } + // 如果count还没满,那么就可以插入数据到数组中 + // 位置不合法 + if (index < 0 || index > count) { + System.out.println("位置不合法"); + return false; + } + // 位置合法 + for (int i = count; i > index; --i) { + data[i] = data[i - 1]; + } + data[index] = value; + ++count; + return true; + } + + //根据索引,删除数组中元素 + public boolean delete(int index) { + if (index < 0 || index >= count) return false; + //从删除位置开始,将后面的元素向前移动一位 + for (int i = index + 1; i < count; ++i) { + data[i - 1] = data[i]; + } + //删除数组末尾元素 这段代码不需要也可以 + /*int[] arr = new int[count-1]; + for (int i=0; i { + + private T[] data; + private int size; + + // 根据传入容量,构造Array + public 模拟ArrayList2(int capacity) { + data = (T[]) new Object[capacity]; + size = 0; + } + + // 无参构造方法,默认数组容量为10 + public 模拟ArrayList2() { + this(10); + } + + // 获取数组容量 + public int getCapacity() { + return data.length; + } + + // 获取当前元素个数 + public int count() { + return size; + } + + // 判断数组是否为空 + public boolean isEmpty() { + return size == 0; + } + + // 修改 index 位置的元素 + public void set(int index, T e) { + checkIndex(index); + data[index] = e; + } + + // 获取对应 index 位置的元素 + public T get(int index) { + checkIndex(index); + return data[index]; + } + + // 查看数组是否包含元素e + public boolean contains(T e) { + for (int i = 0; i < size; i++) { + if (data[i].equals(e)) { + return true; + } + } + return false; + } + + // 获取对应元素的下标, 未找到,返回 -1 + public int find(T e) { + for (int i = 0; i < size; i++) { + if (data[i].equals(e)) { + return i; + } + } + return -1; + } + + // 在 index 位置,插入元素e, 时间复杂度 O(m+n) + public void add(int index, T e) { + checkIndexForAdd(index); + // 如果当前元素个数等于数组容量,则将数组扩容为原来的2倍 + if (size == data.length) { + resize(2 * data.length); + } + + for (int i = size - 1; i >= index; i--) { + data[i + 1] = data[i]; + } + data[index] = e; + size++; + } + + // 向数组头插入元素 + public void addFirst(T e) { + add(0, e); + } + + // 向数组尾插入元素 + public void addLast(T e) { + add(size, e); + } + + // 删除 index 位置的元素,并返回 + public T remove(int index) { + checkIndex(index); + + T ret = data[index]; + for (int i = index + 1; i < size; i++) { + data[i - 1] = data[i]; + } + size--; + data[size] = null; + + // 缩容 + if (size == data.length / 4 && data.length / 2 != 0) { + resize(data.length / 2); + } + + return ret; + } + + // 删除第一个元素 + public T removeFirst() { + return remove(0); + } + + // 删除末尾元素 + public T removeLast() { + return remove(size - 1); + } + + // 从数组中删除指定元素 + public void removeElement(T e) { + int index = find(e); + if (index != -1) { + remove(index); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(String.format("Array size = %d, capacity = %d \n", size, data.length)); + builder.append('['); + for (int i = 0; i < size; i++) { + builder.append(data[i]); + if (i != size - 1) { + builder.append(", "); + } + } + builder.append(']'); + return builder.toString(); + } + + // 扩容方法,时间复杂度 O(n) + private void resize(int capacity) { + T[] newData = (T[]) new Object[capacity]; + + for (int i = 0; i < size; i++) { + newData[i] = data[i]; + } + data = newData; + } + + private void checkIndex(int index) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException("Add failed! Require index >=0 and index < size."); + } + } + + private void checkIndexForAdd(int index) { + if (index < 0 || index > size) { + throw new IllegalArgumentException("remove failed! Require index >=0 and index <= size."); + } + } + + public void printAll() { + for (int i = 0; i < this.size; ++i) { + System.out.print(data[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + 模拟ArrayList2 array = new 模拟ArrayList2<>(5); + array.printAll(); + array.add(0, 3); + array.printAll(); + array.add(0, 4); + array.printAll(); + array.add(1, 5); + array.printAll(); + array.add(3, 9); + array.printAll(); + array.add(3, 10); + array.printAll(); + // array.add(0, 3); + // array.printAll(); + array.resize(10); + array.add(0, 3); + array.printAll(); + array.remove(array.count() - 1); + array.printAll(); + array.remove(0); + array.printAll(); + array.removeElement(4); + array.printAll(); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.java" new file mode 100644 index 0000000..2a10b4a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\215\225\350\257\215.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 151. 反转字符串中的单词 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 反转字符串中的单词 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("blue is sky the", s.reverseWords("the sky is blue")); + Assertions.assertEquals("world hello", s.reverseWords(" hello world ")); + Assertions.assertEquals("example good a", s.reverseWords("a good example")); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals("blue is sky the", s2.reverseWords("the sky is blue")); + Assertions.assertEquals("world hello", s2.reverseWords(" hello world ")); + Assertions.assertEquals("example good a", s2.reverseWords("a good example")); + } + + // 利用库函数 + static class Solution { + + public String reverseWords(String s) { + String[] arr = s.trim().split(" "); + StringBuilder sb = new StringBuilder(); + for (int i = arr.length - 1; i >= 0; i--) { + if (arr[i].equals("")) { + continue; + } + sb.append(arr[i]).append(" "); + } + return sb.toString().trim(); + } + + } + + // 双指针 + static class Solution2 { + + public String reverseWords(String s) { + // 删除首尾空格 + s = s.trim(); + int l = s.length() - 1, r = l; + StringBuilder res = new StringBuilder(); + while (l >= 0) { + // 左指针偏移,直到遇到空格 + while (l >= 0 && s.charAt(l) != ' ') { l--; } + // 添加单词 + res.append(s.substring(l + 1, r + 1)).append(' '); + // 左指针偏移,直到遇到非空格 + while (l >= 0 && s.charAt(l) == ' ') { l--; } + // 右指针对齐左指针 + r = l; + } + return res.toString().trim(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" new file mode 100644 index 0000000..9d3c672 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\345\257\271\350\247\222\347\272\277\351\201\215\345\216\206.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 498. 对角线遍历 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 对角线遍历 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] input = { { 1, 2 }, { 3, 4 } }; + int[] expect = { 1, 2, 3, 4 }; + + int[][] input2 = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + int[] expect2 = { 1, 2, 4, 7, 5, 3, 6, 8, 9 }; + + int[][] input3 = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; + int[] expect3 = { 1, 2, 5, 9, 6, 3, 4, 7, 10, 13, 14, 11, 8, 12, 15, 16 }; + + Assertions.assertArrayEquals(expect, s.findDiagonalOrder(input)); + Assertions.assertArrayEquals(expect2, s.findDiagonalOrder(input2)); + Assertions.assertArrayEquals(expect3, s.findDiagonalOrder(input3)); + } + + static class Solution { + + // 1. 同一对角线上的元素,满足 i + j = k + // 2. k 的大小,满足递增,从 0 到 m + n - 2 + // 3. 由于,i + j = k -> i = k - j + // i = m - 1 时最大,j 最小;而 k - (m - 1) 必须大于 0 => minJ = max(0, k - (m - 1)) + // i = 0 时最小,j 最大,但不能超过 n - 1 => maxJ = Math.max(k, n -1) + public int[] findDiagonalOrder(int[][] mat) { + + // base case + if (mat == null || mat.length == 0) { return new int[0]; } + + int idx = 0; + int m = mat.length, n = mat[0].length; + int[] res = new int[m * n]; + for (int k = 0; k < m + n - 1; k++) { + int minJ = Math.max(k - (m - 1), 0); + int maxJ = Math.min(k, n - 1); + if (k % 2 == 0) { + for (int j = minJ; j <= maxJ; j++) { + res[idx++] = mat[k - j][j]; + } + } else { + for (int j = maxJ; j >= minJ; j--) { + res[idx++] = mat[k - j][j]; + } + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\346\227\213\350\275\254\345\233\276\345\203\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\346\227\213\350\275\254\345\233\276\345\203\217.java" new file mode 100644 index 0000000..b4bdd6d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\346\227\213\350\275\254\345\233\276\345\203\217.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.array.matrix; + +import cn.hutool.core.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +/** + * LCR 006. 两数之和 II - 输入有序数组 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 旋转图像 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] matrix = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + s.rotate(matrix); + int[][] expect = { { 7, 4, 1 }, { 8, 5, 2 }, { 9, 6, 3 } }; + Assertions.assertTrue(ArrayUtil.equals(expect, matrix)); + + int[][] matrix2 = { { 5, 1, 9, 11 }, { 2, 4, 8, 10 }, { 13, 3, 6, 7 }, { 15, 14, 12, 16 } }; + s.rotate(matrix2); + int[][] expect2 = { { 15, 13, 2, 5 }, { 14, 3, 4, 1 }, { 12, 6, 8, 9 }, { 16, 7, 10, 11 } }; + Assertions.assertTrue(ArrayUtil.equals(expect2, matrix2)); + } + + static class Solution { + + public void rotate(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return; } + int n = matrix.length; + // 沿对角线置换 + for (int i = 0; i < n; i++) { + for (int j = 0; j < i; j++) { + int temp = matrix[i][j]; + matrix[i][j] = matrix[j][i]; + matrix[j][i] = temp; + } + } + + for (int i = 0; i < n; i++) { + int left = 0, right = n - 1; + while (left < right) { + int temp = matrix[i][left]; + matrix[i][left] = matrix[i][right]; + matrix[i][right] = temp; + left++; + right--; + } + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\265.java" new file mode 100644 index 0000000..e0085df --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\265.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 54. 螺旋矩阵 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 螺旋矩阵 { + + public static void main(String[] args) { + Solution s = new Solution(); + List output = s.spiralOrder(new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3, 6, 9, 8, 7, 4, 5 }, output.toArray()); + List output2 = s.spiralOrder(new int[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7 }, output2.toArray()); + } + + static class Solution { + + public List spiralOrder(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { + return new LinkedList<>(); + } + + int m = matrix.length, n = matrix[0].length; + int up = 0, down = m - 1, left = 0, right = n - 1; + List res = new LinkedList<>(); + while (res.size() < m * n) { + // 向右 + if (up <= down) { + for (int i = left; i <= right; i++) { + res.add(matrix[up][i]); + } + up++; + } + // System.out.printf("\t [right] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向下 + if (left <= right) { + for (int i = up; i <= down; i++) { + res.add(matrix[i][right]); + } + right--; + } + // System.out.printf("\t [down] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向左 + if (up <= down) { + for (int i = right; i >= left; i--) { + res.add(matrix[down][i]); + } + down--; + } + // System.out.printf("\t [left] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向上 + if (left <= right) { + for (int i = down; i >= up; i--) { + res.add(matrix[i][left]); + } + left++; + } + // System.out.printf("\t [up] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // System.out.printf("res: %s\n", JSONUtil.toJsonStr(res)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\2652.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\2652.java" new file mode 100644 index 0000000..785f236 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\347\237\251\351\230\2652.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 54. 螺旋矩阵 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 螺旋矩阵2 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] output = s.generateMatrix(3); + Assertions.assertArrayEquals(new int[][] { { 1, 2, 3 }, { 8, 9, 4 }, { 7, 6, 5 } }, output); + int[][] output2 = s.generateMatrix(1); + Assertions.assertArrayEquals(new int[][] { { 1 } }, output2); + } + + static class Solution { + + public int[][] generateMatrix(int n) { + int cnt = 0; + int[][] res = new int[n][n]; + int left = 0, right = n - 1, top = 0, bottom = n - 1; + while (cnt < n * n) { + + // 向右 + if (top <= bottom) { + for (int i = left; i <= right; i++) { + res[top][i] = ++cnt; + } + top++; + } + + // 向下 + if (left <= right) { + for (int i = top; i <= bottom; i++) { + res[i][right] = ++cnt; + } + right--; + } + + // 向左 + if (top <= bottom) { + for (int i = right; i >= left; i--) { + res[bottom][i] = ++cnt; + } + bottom--; + } + + // 向上 + if (left <= right) { + for (int i = bottom; i >= top; i--) { + res[i][left] = ++cnt; + } + left++; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\351\201\215\345\216\206\344\272\214\347\273\264\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\351\201\215\345\216\206\344\272\214\347\273\264\346\225\260\347\273\204.java" new file mode 100644 index 0000000..c39ed1a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\350\236\272\346\227\213\351\201\215\345\216\206\344\272\214\347\273\264\346\225\260\347\273\204.java" @@ -0,0 +1,84 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 54. 螺旋矩阵 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 螺旋遍历二维数组 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[] output = s.spiralArray(new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }); + Assertions.assertArrayEquals(new int[] { 1, 2, 3, 6, 9, 8, 7, 4, 5 }, output); + int[] output2 = s.spiralArray(new int[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }); + Assertions.assertArrayEquals(new int[] { 1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7 }, output2); + } + + static class Solution { + + public int[] spiralArray(int[][] array) { + List list = spiralOrder(array); + int[] res = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + res[i] = list.get(i); + } + return res; + } + + public List spiralOrder(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { + return new LinkedList<>(); + } + + int m = matrix.length, n = matrix[0].length; + int up = 0, down = m - 1; + int left = 0, right = n - 1; + List res = new LinkedList<>(); + while (res.size() < m * n) { + // 向右 + if (up <= down) { + for (int i = left; i <= right; i++) { + res.add(matrix[up][i]); + } + up++; + } + // System.out.printf("\t [right] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向下 + if (left <= right) { + for (int i = up; i <= down; i++) { + res.add(matrix[i][right]); + } + right--; + } + // System.out.printf("\t [down] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向左 + if (up <= down) { + for (int i = right; i >= left; i--) { + res.add(matrix[down][i]); + } + down--; + } + // System.out.printf("\t [left] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // 向上 + if (left <= right) { + for (int i = down; i >= up; i--) { + res.add(matrix[i][left]); + } + left++; + } + // System.out.printf("\t [up] up: %d, down: %d, left: %d, right: %d\n", up, down, left, right); + // System.out.printf("res: %s\n", JSONUtil.toJsonStr(res)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\351\233\266\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\351\233\266\347\237\251\351\230\265.java" new file mode 100644 index 0000000..6c7d535 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/matrix/\351\233\266\347\237\251\351\230\265.java" @@ -0,0 +1,54 @@ +package io.github.dunwu.algorithm.array.matrix; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 08. 零矩阵 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 零矩阵 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input = { + { 1, 1, 1 }, + { 1, 0, 1 }, + { 1, 1, 1 } + }; + int[][] expect = { + { 1, 0, 1 }, + { 0, 0, 0 }, + { 1, 0, 1 } + }; + s.setZeroes(input); + Assertions.assertArrayEquals(expect, input); + } + + static class Solution { + + public void setZeroes(int[][] matrix) { + int m = matrix.length, n = matrix[0].length; + LinkedList queue = new LinkedList<>(); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (matrix[i][j] == 0) { + queue.offer(new int[] { i, j }); + } + } + } + + while (!queue.isEmpty()) { + int[] point = queue.poll(); + int x = point[0], y = point[1]; + for (int i = 0; i < n; i++) { matrix[x][i] = 0; } + for (int i = 0; i < m; i++) { matrix[i][y] = 0; } + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.java" new file mode 100644 index 0000000..61ece6f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\344\272\214\347\273\264\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\347\237\251\351\230\265\344\270\215\345\217\257\345\217\230.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 303. 区域和检索 - 数组不可变 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 二维区域和检索_矩阵不可变 { + + public static void main(String[] args) { + NumMatrix numMatrix = new NumMatrix(new int[][] { + { 3, 0, 1, 4, 2 }, + { 5, 6, 3, 2, 1 }, + { 1, 2, 0, 1, 5 }, + { 4, 1, 0, 1, 7 }, + { 1, 0, 3, 0, 5 } + }); + Assertions.assertEquals(8, numMatrix.sumRegion(2, 1, 4, 3)); + Assertions.assertEquals(11, numMatrix.sumRegion(1, 1, 2, 2)); + Assertions.assertEquals(12, numMatrix.sumRegion(1, 2, 2, 4)); + } + + static class NumMatrix { + + // preSum[i][j] 记录矩阵 [0, 0, i-1, j-1] 的元素和 + private int[][] preSum; + + public NumMatrix(int[][] matrix) { + int m = matrix.length, n = matrix[0].length; + if (m == 0 || n == 0) return; + // 构造前缀和矩阵 + preSum = new int[m + 1][n + 1]; + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + // 计算每个矩阵 [0, 0, i, j] 的元素和 + preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] + matrix[i - 1][j - 1] - preSum[i - 1][j - 1]; + } + } + } + + // 计算子矩阵 [x1, y1, x2, y2] 的元素和 + public int sumRegion(int x1, int y1, int x2, int y2) { + // 目标矩阵之和由四个相邻矩阵运算获得 + return preSum[x2 + 1][y2 + 1] - preSum[x1][y2 + 1] - preSum[x2 + 1][y1] + preSum[x1][y1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.java" new file mode 100644 index 0000000..276664c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\214\272\345\237\237\345\222\214\346\243\200\347\264\242_\346\225\260\347\273\204\344\270\215\345\217\257\345\217\230.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 303. 区域和检索 - 数组不可变 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 区域和检索_数组不可变 { + + public static void main(String[] args) { + NumArray numArray = new NumArray(new int[] { -2, 0, 3, -5, 2, -1 }); + Assertions.assertEquals(1, numArray.sumRange(0, 2)); + Assertions.assertEquals(-1, numArray.sumRange(2, 5)); + Assertions.assertEquals(-3, numArray.sumRange(0, 5)); + } + + static class NumArray { + + private int[] preSum; + + public NumArray(int[] nums) { + preSum = new int[nums.length + 1]; + for (int i = 1; i <= nums.length; i++) { + preSum[i] = preSum[i - 1] + nums[i - 1]; + } + } + + public int sumRange(int left, int right) { + return preSum[right + 1] - preSum[left]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" new file mode 100644 index 0000000..c7d8c6b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\347\264\242\345\274\225.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 724. 寻找数组的中心索引 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 寻找数组的中心索引 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.pivotIndex(new int[] { 1, 7, 3, 6, 5, 6 })); + Assertions.assertEquals(-1, s.pivotIndex(new int[] { 1, 2, 3 })); + Assertions.assertEquals(0, s.pivotIndex(new int[] { 2, 1, -1 })); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(3, s2.pivotIndex(new int[] { 1, 7, 3, 6, 5, 6 })); + Assertions.assertEquals(-1, s2.pivotIndex(new int[] { 1, 2, 3 })); + Assertions.assertEquals(0, s2.pivotIndex(new int[] { 2, 1, -1 })); + } + + static class Solution { + + public int pivotIndex(int[] nums) { + for (int mid = 0; mid < nums.length; mid++) { + int leftSum = 0, rightSum = 0; + for (int i = 0; i < mid; i++) { + leftSum += nums[i]; + } + for (int i = mid + 1; i < nums.length; i++) { + rightSum += nums[i]; + } + if (leftSum == rightSum) { + return mid; + } + } + return -1; + } + + } + + static class Solution2 { + + public int pivotIndex(int[] nums) { + int total = 0; + for (int num : nums) { + total += num; + } + + int leftSum = 0; + for (int i = 0; i < nums.length; i++) { + int rightSum = total - leftSum - nums[i]; + if (leftSum == rightSum) { + return i; + } + leftSum += nums[i]; + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" new file mode 100644 index 0000000..43dca82 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\345\260\206\346\225\260\347\273\204\345\210\206\346\210\220\345\222\214\347\233\270\347\255\211\347\232\204\344\270\211\344\270\252\351\203\250\345\210\206.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 1013.将数组分成和相等的三个部分 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 将数组分成和相等的三个部分 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canThreePartsEqualSum(new int[] { 0, 2, 1, -6, 6, -7, 9, 1, 2, 0, 1 })); + Assertions.assertTrue(s.canThreePartsEqualSum(new int[] { 3, 3, 6, 5, -2, 2, 5, 1, -9, 4 })); + Assertions.assertFalse(s.canThreePartsEqualSum(new int[] { 0, 2, 1, -6, 6, 7, 9, -1, 2, 0, 1 })); + } + + static class Solution { + + public boolean canThreePartsEqualSum(int[] arr) { + int n = arr.length; + NumArray preSum = new NumArray(arr); + for (int i = 1; i < n; i++) { + for (int j = i + 1; j < n; j++) { + int leftSum = preSum.sumRange(0, i - 1); + int midSum = preSum.sumRange(i, j - 1); + int rightSum = preSum.sumRange(j, n - 1); + if (leftSum == midSum && midSum == rightSum) { + return true; + } + } + } + return false; + } + + static class NumArray { + + private final int[] preSum; + + public NumArray(int[] arr) { + preSum = new int[arr.length + 1]; + preSum[0] = 0; + for (int i = 1; i <= arr.length; i++) { + preSum[i] = preSum[i - 1] + arr[i - 1]; + } + } + + public int sumRange(int left, int right) { + return preSum[right + 1] - preSum[left]; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\346\213\274\350\275\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\346\213\274\350\275\246.java" new file mode 100644 index 0000000..6ea1646 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\346\213\274\350\275\246.java" @@ -0,0 +1,94 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 1094. 屁车 + * + * @author Zhang Peng + * @date 2025-10-17 + */ +public class 拼车 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input = { { 2, 1, 5 }, { 3, 3, 7 } }; + Assertions.assertFalse(s.carPooling(input, 3)); + int[][] input2 = { { 1, 2, 10 }, { 2, 2, 15 } }; + Assertions.assertTrue(s.carPooling(input2, 5)); + int[][] input3 = { { 2, 1, 5 }, { 3, 5, 7 } }; + Assertions.assertTrue(s.carPooling(input3, 3)); + } + + static class Solution { + + public boolean carPooling(int[][] trips, int capacity) { + // 最多有 1000 个车站 + int[] nums = new int[1001]; + // 构造差分解法 + Difference df = new Difference(nums); + + for (int[] trip : trips) { + // 乘客数量 + int val = trip[0]; + // 第 trip[1] 站乘客上车 + int i = trip[1]; + // 第 trip[2] 站乘客已经下车, + // 即乘客在车上的区间是 [trip[1], trip[2] - 1] + int j = trip[2] - 1; + // 进行区间操作 + df.increment(i, j, val); + } + + int[] res = df.result(); + + // 客车自始至终都不应该超载 + for (int i = 0; i < res.length; i++) { + if (capacity < res[i]) { + return false; + } + } + return true; + } + + // 差分数组工具类 + class Difference { + + // 差分数组 + private int[] diff; + + // 输入一个初始数组,区间操作将在这个数组上进行 + public Difference(int[] nums) { + assert nums.length > 0; + diff = new int[nums.length]; + // 根据初始数组构造差分数组 + diff[0] = nums[0]; + for (int i = 1; i < nums.length; i++) { + diff[i] = nums[i] - nums[i - 1]; + } + } + + // 给闭区间 [i, j] 增加 val(可以是负数) + public void increment(int i, int j, int val) { + diff[i] += val; + if (j + 1 < diff.length) { + diff[j + 1] -= val; + } + } + + // 返回结果数组 + public int[] result() { + int[] res = new int[diff.length]; + // 根据差分数组构造结果数组 + res[0] = diff[0]; + for (int i = 1; i < diff.length; i++) { + res[i] = res[i - 1] + diff[i]; + } + return res; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.java" new file mode 100644 index 0000000..bb51080 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/range/\350\210\252\347\217\255\351\242\204\350\256\242\347\273\237\350\256\241.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.array.range; + +import org.junit.jupiter.api.Assertions; + +/** + * 1109. 航班预订统计 + * + * @author Zhang Peng + * @date 2025-10-17 + */ +public class 航班预订统计 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] bookings = { { 1, 2, 10 }, { 2, 3, 20 }, { 2, 5, 25 } }; + Assertions.assertArrayEquals(new int[] { 10, 55, 45, 25, 25 }, s.corpFlightBookings(bookings, 5)); + + int[][] bookings2 = { { 1, 2, 10 }, { 2, 2, 15 } }; + Assertions.assertArrayEquals(new int[] { 10, 25 }, s.corpFlightBookings(bookings2, 2)); + } + + static class Solution { + + public int[] corpFlightBookings(int[][] bookings, int n) { + int[] nums = new int[n]; + Difference df = new Difference(nums); + for (int[] booking : bookings) { + int first = booking[0], last = booking[1], seat = booking[2]; + df.increment(first - 1, last - 1, seat); + } + return df.result(); + } + + // 差分数组工具类 + static class Difference { + + // 差分数组 + private final int[] diff; + + // 输入一个初始数组,区间操作将在这个数组上进行 + public Difference(int[] nums) { + assert nums.length > 0; + diff = new int[nums.length]; + // 根据初始数组构造差分数组 + diff[0] = nums[0]; + for (int i = 1; i < nums.length; i++) { + diff[i] = nums[i] - nums[i - 1]; + } + } + + // 给闭区间 [i, j] 增加 val(可以是负数) + public void increment(int i, int j, int val) { + diff[i] += val; + if (j + 1 < diff.length) { + diff[j + 1] -= val; + } + } + + // 返回结果数组 + public int[] result() { + int[] res = new int[diff.length]; + // 根据差分数组构造结果数组 + res[0] = diff[0]; + for (int i = 1; i < diff.length; i++) { + res[i] = res[i - 1] + diff[i]; + } + return res; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\344\272\214\345\210\206\346\237\245\346\211\276\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\344\272\214\345\210\206\346\237\245\346\211\276\346\250\241\346\235\277.java" new file mode 100644 index 0000000..7f21032 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\344\272\214\345\210\206\346\237\245\346\211\276\346\250\241\346\235\277.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.array.template; + +/** + * 二分查找模板 + * + * @author Zhang Peng + * @date 2025-12-08 + */ +public class 二分查找模板 { + + int binary_search(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + // 直接返回 + return mid; + } + } + // 直接返回 + return -1; + } + + int left_bound(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + // 别返回,锁定左侧边界 + right = mid - 1; + } + } + // 判断 target 是否存在于 nums 中 + if (left < 0 || left >= nums.length) { + return -1; + } + // 判断一下 nums[left] 是不是 target + return nums[left] == target ? left : -1; + } + + int right_bound(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] > target) { + right = mid - 1; + } else if (nums[mid] == target) { + // 别返回,锁定右侧边界 + left = mid + 1; + } + } + // 由于 while 的结束条件是 right == left - 1,且现在在求右边界 + // 所以用 right 替代 left - 1 更好记 + if (right < 0 || right >= nums.length) { + return -1; + } + return nums[right] == target ? right : -1; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" new file mode 100644 index 0000000..7c37f55 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\211\215\347\274\200\345\222\214\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.array.template; + +/** + * 前缀和数组代码模板 + * + * @author Zhang Peng + * @date 2025-10-20 + */ +public class 前缀和数组代码模板 { + + /** + * 一维前缀和 + */ + static class NumArray { + + // 前缀和数组 + private final int[] preSum; + + // 输入一个数组,构造前缀和 + public NumArray(int[] nums) { + // preSum[0] = 0,便于计算累加和 + preSum = new int[nums.length + 1]; + // 计算 nums 的累加和 + for (int i = 1; i < preSum.length; i++) { + preSum[i] = preSum[i - 1] + nums[i - 1]; + } + } + + // 查询闭区间 [left, right] 的累加和 + public int sumRange(int left, int right) { + return preSum[right + 1] - preSum[left]; + } + + } + + /** + * 二维前缀和 + */ + static class NumMatrix { + + // preSum[i][j] 记录矩阵 [0, 0, i-1, j-1] 的元素和 + private int[][] preSum; + + public NumMatrix(int[][] matrix) { + int m = matrix.length, n = matrix[0].length; + if (m == 0 || n == 0) return; + // 构造前缀和矩阵 + preSum = new int[m + 1][n + 1]; + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + // 计算每个矩阵 [0, 0, i, j] 的元素和 + preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] + matrix[i - 1][j - 1] - preSum[i - 1][j - 1]; + } + } + } + + // 计算子矩阵 [x1, y1, x2, y2] 的元素和 + public int sumRegion(int x1, int y1, int x2, int y2) { + // 目标矩阵之和由四个相邻矩阵运算获得 + return preSum[x2 + 1][y2 + 1] - preSum[x1][y2 + 1] - preSum[x2 + 1][y1] + preSum[x1][y1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\267\256\345\210\206\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\267\256\345\210\206\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" new file mode 100644 index 0000000..bb6a01c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\345\267\256\345\210\206\346\225\260\347\273\204\344\273\243\347\240\201\346\250\241\346\235\277.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.array.template; + +/** + * 差分数组代码模板 + * + * @author Zhang Peng + * @date 2025-10-20 + */ +public class 差分数组代码模板 { + + // 差分数组工具类 + static class Difference { + + // 差分数组 + private final int[] diff; + + // 输入一个初始数组,区间操作将在这个数组上进行 + public Difference(int[] nums) { + assert nums.length > 0; + diff = new int[nums.length]; + // 根据初始数组构造差分数组 + diff[0] = nums[0]; + for (int i = 1; i < nums.length; i++) { + diff[i] = nums[i] - nums[i - 1]; + } + } + + // 给闭区间 [i, j] 增加 val(可以是负数) + public void increment(int i, int j, int val) { + diff[i] += val; + if (j + 1 < diff.length) { + diff[j + 1] -= val; + } + } + + // 返回结果数组 + public int[] result() { + int[] res = new int[diff.length]; + // 根据差分数组构造结果数组 + res[0] = diff[0]; + for (int i = 1; i < diff.length; i++) { + res[i] = res[i - 1] + diff[i]; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\346\273\221\345\212\250\347\252\227\345\217\243\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\346\273\221\345\212\250\347\252\227\345\217\243\346\250\241\346\235\277.java" new file mode 100644 index 0000000..66a46d4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/template/\346\273\221\345\212\250\347\252\227\345\217\243\346\250\241\346\235\277.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.array.template; + +/** + * 滑动窗口模板 + * + * @author Zhang Peng + * @date 2025-11-20 + */ +public class 滑动窗口模板 { + + // 滑动窗口算法伪码框架 + // void slidingWindow(String s) { + // // 用合适的数据结构记录窗口中的数据,根据具体场景变通 + // // 比如说,我想记录窗口中元素出现的次数,就用 map + // // 如果我想记录窗口中的元素和,就可以只用一个 int + // Object window = ... + // + // int left = 0, right = 0; + // while (right < s.length()) { + // // c 是将移入窗口的字符 + // char c = s[right]; + // window.add(c) + // // 增大窗口 + // right++; + // // 进行窗口内数据的一系列更新 + // // ... + // + // // *** debug 输出的位置 *** + // // 注意在最终的解法代码中不要 print + // // 因为 IO 操作很耗时,可能导致超时 + // printf("window: [%d, %d)\n", left, right); + // // *********************** + // + // // 判断左侧窗口是否要收缩 + // while (left < right && window needs shrink){ + // // d 是将移出窗口的字符 + // char d = s[left]; + // window.remove(d) + // // 缩小窗口 + // left++; + // // 进行窗口内数据的一系列更新 + // ... + // } + // } + // } +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\211\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\211\346\225\260\344\271\213\345\222\214.java" new file mode 100644 index 0000000..bb147b7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\211\346\225\260\344\271\213\345\222\214.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * + * 三数之和 + * + * @author Zhang Peng + * @since 2020-01-18 + */ +public class 三数之和 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> input = s.threeSum(new int[] { -1, 0, 1, 2, -1, -4 }); + List> expect = new ArrayList<>(); + expect.add(Arrays.asList(-1, -1, 2)); + expect.add(Arrays.asList(-1, 0, 1)); + Assertions.assertArrayEquals(expect.toArray(), input.toArray()); + } + + static class Solution { + + public List> threeSum(int[] nums) { + if (nums == null || nums.length < 3) { return new ArrayList<>(); } + + // 数组排序 + Arrays.sort(nums); + + List> res = new ArrayList<>(); + for (int i = 0; i < nums.length; i++) { + + // 跳过重复元素 + if (i > 0 && nums[i] == nums[i - 1]) { continue; } + + // 双指针,目标是找到 nums[l] + nums[r] = -nums[i] + int target = -nums[i]; + int l = i + 1, r = nums.length - 1; + + while (l < r) { + int sum = nums[l] + nums[r]; + if (sum == target) { + res.add(Arrays.asList(nums[i], nums[l], nums[r])); + l++; + r--; + // 跳过重复元素 + while (l < r && nums[l] == nums[l - 1]) l++; + while (l < r && nums[r] == nums[r + 1]) r--; + } else if (sum > target) { + r--; + } else if (sum < target) { + l++; + } + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\214.java" new file mode 100644 index 0000000..27e751f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\214.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 1. 两数之和 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 两数之和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s.twoSum(new int[] { 2, 7, 11, 15 }, 9)); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s.twoSum(new int[] { 3, 2, 4 }, 6)); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s.twoSum(new int[] { 3, 3 }, 6)); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s2.twoSum(new int[] { 2, 7, 11, 15 }, 9)); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s2.twoSum(new int[] { 3, 2, 4 }, 6)); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s2.twoSum(new int[] { 3, 3 }, 6)); + } + + /** + * 两次 for 循环暴力求解,时间复杂度 o(n^2) + */ + static class Solution { + + public int[] twoSum(int[] nums, int target) { + int n = nums.length; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (nums[i] + nums[j] == target) { + return new int[] { i, j }; + } + } + } + return new int[0]; + } + + } + + /** + * Hash 存值、下标,一次 for 循环,每次判断 map 中是否有值和当前下标的值凑成 target + */ + static class Solution2 { + + public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(nums.length); + for (int i = 0; i < nums.length; i++) { + int diff = target - nums[i]; + if (map.containsKey(diff)) { + return new int[] { map.get(diff), i }; + } else { + map.put(nums[i], i); + } + } + return new int[0]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\2142.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\2142.java" new file mode 100644 index 0000000..2585dda --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\270\244\346\225\260\344\271\213\345\222\2142.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 167. 两数之和 II - 输入有序数组 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 两数之和2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s.twoSum(new int[] { 2, 7, 11, 15 }, 9)); + Assertions.assertArrayEquals(new int[] { 1, 3 }, s.twoSum(new int[] { 2, 3, 4 }, 6)); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s.twoSum(new int[] { -1, 0 }, -1)); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s2.twoSum(new int[] { 2, 7, 11, 15 }, 9)); + Assertions.assertArrayEquals(new int[] { 1, 3 }, s2.twoSum(new int[] { 2, 3, 4 }, 6)); + Assertions.assertArrayEquals(new int[] { 1, 2 }, s2.twoSum(new int[] { -1, 0 }, -1)); + } + + /** + * Hash 存值、下标,一次 for 循环,每次判断 map 中是否有值和当前下标的值凑成 target + */ + static class Solution { + + public int[] twoSum(int[] nums, int target) { + Map map = new HashMap<>(nums.length); + for (int i = 0; i < nums.length; i++) { + int diff = target - nums[i]; + if (map.containsKey(diff)) { + return new int[] { map.get(diff), i + 1 }; + } else { + map.put(nums[i], i + 1); + } + } + return new int[0]; + } + + } + + /** + * 双指针 + */ + static class Solution2 { + + public int[] twoSum(int[] nums, int target) { + int left = 0, right = nums.length - 1; + while (left < right) { + if (nums[left] + nums[right] == target) { + return new int[] { left + 1, right + 1 }; + } else if (nums[left] + nums[right] < target) { + left++; + } else { + right--; + } + } + return new int[0]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\347\273\264\347\275\221\346\240\274\350\277\201\347\247\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\347\273\264\347\275\221\346\240\274\350\277\201\347\247\273.java" new file mode 100644 index 0000000..1e7c9e1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\347\273\264\347\275\221\346\240\274\350\277\201\347\247\273.java" @@ -0,0 +1,92 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 1260. 二维网格迁移 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 二维网格迁移 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] grid1 = new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + List> res1 = s.shiftGrid(grid1, 1); + Assertions.assertNotNull(res1); + Assertions.assertArrayEquals(new Integer[] { 9, 1, 2 }, res1.get(0).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 3, 4, 5 }, res1.get(1).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 6, 7, 8 }, res1.get(2).toArray(new Integer[0])); + + int[][] grid2 = new int[][] { { 3, 8, 1, 9 }, { 19, 7, 2, 5 }, { 4, 6, 11, 10 }, { 12, 0, 21, 13 } }; + List> res2 = s.shiftGrid(grid2, 4); + Assertions.assertNotNull(res2); + Assertions.assertArrayEquals(new Integer[] { 12, 0, 21, 13 }, res2.get(0).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 3, 8, 1, 9 }, res2.get(1).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 19, 7, 2, 5 }, res2.get(2).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 4, 6, 11, 10 }, res2.get(3).toArray(new Integer[0])); + + int[][] grid3 = new int[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + List> res3 = s.shiftGrid(grid3, 9); + Assertions.assertNotNull(res3); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, res3.get(0).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 4, 5, 6 }, res3.get(1).toArray(new Integer[0])); + Assertions.assertArrayEquals(new Integer[] { 7, 8, 9 }, res3.get(2).toArray(new Integer[0])); + + int[][] grid4 = new int[][] { { 1 }, { 2 }, { 3 }, { 4 }, { 7 }, { 6 }, { 5 } }; + List> res4 = s.shiftGrid(grid4, 23); + Assertions.assertNotNull(res4); + } + + static class Solution { + + public List> shiftGrid(int[][] grid, int k) { + for (int i = 0; i < k; i++) { + shift(grid); + } + + int m = grid.length, n = grid[0].length; + List> res = new ArrayList<>(); + for (int i = 0; i < m; i++) { + List list = new ArrayList<>(); + res.add(list); + for (int j = 0; j < n; j++) { + list.add(grid[i][j]); + } + } + return res; + } + + public void shift(int[][] grid) { + int m = grid.length, n = grid[0].length; + int last = get(grid, m * n - 1); + for (int i = m * n - 1; i > 0; i--) { + int prev = get(grid, i - 1); + set(grid, i, prev); + } + set(grid, 0, last); + } + + public int get(int[][] grid, int index) { + int n = grid[0].length; + int i = index / n; + int j = index % n; + return grid[i][j]; + } + + public void set(int[][] grid, int index, int val) { + int n = grid[0].length; + int i = index / n; + int j = index % n; + grid[i][j] = val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.java" new file mode 100644 index 0000000..812604b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\344\272\214\350\277\233\345\210\266\346\261\202\345\222\214.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 67. 二进制求和 + * + * @author Zhang Peng + * @date 2025-01-21 + */ +public class 二进制求和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("100", s.addBinary("11", "1")); + Assertions.assertEquals("10101", s.addBinary("1010", "1011")); + } + + static class Solution { + + public String addBinary(String a, String b) { + int i = a.length() - 1; + int j = b.length() - 1; + int carry = 0; + StringBuilder sb = new StringBuilder(); + while (i >= 0 || j >= 0) { + int numA = i < 0 ? 0 : a.charAt(i--) - '0'; + int numB = j < 0 ? 0 : b.charAt(j--) - '0'; + int sum = numA + numB + carry; + if (sum > 1) { + carry = 1; + sb.append(sum % 2); + } else { + carry = 0; + sb.append(sum); + } + } + if (carry > 0) { + sb.append(carry); + } + return sb.reverse().toString(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" new file mode 100644 index 0000000..ce195a8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 26. 删除有序数组中的重复项 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 删除排序数组中的重复项 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.removeDuplicates(new int[] { 1, 1, 2 })); + Assertions.assertEquals(5, s.removeDuplicates(new int[] { 0, 0, 1, 1, 1, 2, 2, 3, 3, 4 })); + Assertions.assertEquals(2, s.removeDuplicates(new int[] { 1, 2 })); + Assertions.assertEquals(1, s.removeDuplicates(new int[] { 2, 2 })); + } + + static class Solution { + + public int removeDuplicates(int[] nums) { + int slow = 0, fast = 1; + while (fast < nums.length) { + if (nums[fast] != nums[slow]) { + slow++; + nums[slow] = nums[fast]; + } + fast++; + } + return slow + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\2712.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\2712.java" new file mode 100644 index 0000000..ad47707 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\2712.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 80. 删除有序数组中的重复项 II + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 删除排序数组中的重复项2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.removeDuplicates(new int[] { 1, 1, 1, 2, 2, 3 })); + Assertions.assertEquals(7, s.removeDuplicates(new int[] { 0, 0, 1, 1, 1, 1, 2, 3, 3 })); + } + + static class Solution { + + public int removeDuplicates(int[] nums) { + int slow = 0, fast = 1; + int cnt = 1; + while (fast < nums.length) { + if (nums[fast] != nums[slow]) { + cnt = 1; + slow++; + nums[slow] = nums[fast]; + } else { + if (cnt < 2) { + slow++; + nums[slow] = nums[fast]; + } + cnt++; + } + fast++; + } + // System.out.printf("slow: %d, fast: %d, nums: %s\n", slow, fast, + // JSONUtil.toJsonStr(ArrayUtil.sub(nums, 0, slow + 1))); + return slow + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" new file mode 100644 index 0000000..75e2bc1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 344. 反转字符串 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 反转字符串 { + + public static void main(String[] args) { + Solution s = new Solution(); + char[] arr1 = new char[] { 'h', 'e', 'l', 'l', 'o' }; + s.reverseString(arr1); + Assertions.assertArrayEquals(new char[] { 'o', 'l', 'l', 'e', 'h' }, arr1); + + char[] arr2 = new char[] { 'H', 'a', 'n', 'n', 'a', 'h' }; + s.reverseString(arr2); + Assertions.assertArrayEquals(new char[] { 'h', 'a', 'n', 'n', 'a', 'H' }, arr2); + } + + static class Solution { + + public void reverseString(char[] s) { + int left = 0, right = s.length - 1; + while (left < right) { + char temp = s[left]; + s[left] = s[right]; + s[right] = temp; + left++; + right--; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.java" new file mode 100644 index 0000000..7465ea6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 88. 合并两个有序数组 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 合并两个有序数组 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] nums1 = new int[] { 1, 2, 3, 0, 0, 0 }; + int[] nums2 = new int[] { 2, 5, 6 }; + s.merge(nums1, 3, nums2, 3); + Assertions.assertArrayEquals(new int[] { 1, 2, 2, 3, 5, 6 }, nums1); + + int[] nums3 = new int[] { 1 }; + int[] nums4 = new int[] {}; + s.merge(nums3, 1, nums4, 0); + Assertions.assertArrayEquals(new int[] { 1 }, nums3); + + int[] nums5 = new int[] { 0 }; + int[] nums6 = new int[] { 1 }; + s.merge(nums5, 0, nums6, 1); + Assertions.assertArrayEquals(new int[] { 1 }, nums5); + + int[] nums7 = new int[] { 4, 5, 6, 0, 0, 0 }; + int[] nums8 = new int[] { 1, 2, 3 }; + s.merge(nums7, 3, nums8, 3); + Assertions.assertArrayEquals(new int[] { 1, 2, 3, 4, 5, 6 }, nums7); + } + + static class Solution { + + public void merge(int[] nums1, int m, int[] nums2, int n) { + // 两个指针分别初始化在两个数组的最后一个元素(类似拉链两端的锯齿) + int i = m - 1, j = n - 1; + // 生成排序的结果(类似拉链的拉锁) + int p = nums1.length - 1; + // 从后向前生成结果数组,类似合并两个有序链表的逻辑 + while (i >= 0 && j >= 0) { + if (nums1[i] >= nums2[j]) { + nums1[p] = nums1[i]; + i--; + } else { + nums1[p] = nums2[j]; + j--; + } + p--; + } + // 可能其中一个数组的指针走到尽头了,而另一个还没走完 + while (i >= 0) { + nums1[p--] = nums1[i--]; + } + while (j >= 0) { + nums1[p--] = nums2[j--]; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\345\214\272\351\227\264.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\345\214\272\351\227\264.java" new file mode 100644 index 0000000..d424a3e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\220\210\345\271\266\345\214\272\351\227\264.java" @@ -0,0 +1,62 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 56. 合并区间 + * + * @author Zhang Peng + * @since 2020-07-29 + */ +public class 合并区间 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] input = new int[][] { { 1, 4 }, { 2, 3 } }; + int[][] expect = new int[][] { { 1, 4 } }; + Assertions.assertArrayEquals(expect, s.merge(input)); + + int[][] input2 = new int[][] { { 1, 3 }, { 2, 6 }, { 8, 10 }, { 15, 18 } }; + int[][] expect2 = new int[][] { { 1, 6 }, { 8, 10 }, { 15, 18 } }; + Assertions.assertArrayEquals(expect2, s.merge(input2)); + + int[][] input3 = new int[][] { { 1, 4 }, { 4, 5 } }; + int[][] expect3 = new int[][] { { 1, 5 } }; + Assertions.assertArrayEquals(expect3, s.merge(input3)); + } + + static class Solution { + + public int[][] merge(int[][] intervals) { + + // base case + if (intervals == null || intervals.length <= 1) { return intervals; } + + // 先按区间下限排序 + Arrays.sort(intervals, (a, b) -> a[0] - b[0]); + + // 设置双指针,扫描 intervals + List merged = new ArrayList<>(); + for (int[] interval : intervals) { + int l = interval[0], r = interval[1]; + int last = merged.size() - 1; + if (last == -1 || merged.get(last)[1] < l) { + merged.add(new int[] { l, r }); + } else { + l = merged.get(last)[0]; + r = Math.max(merged.get(last)[1], r); + merged.set(last, new int[] { l, r }); + } + } + return merged.toArray(new int[merged.size()][2]); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\260\206\347\237\251\351\230\265\346\214\211\345\257\271\350\247\222\347\272\277\346\216\222\345\272\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\260\206\347\237\251\351\230\265\346\214\211\345\257\271\350\247\222\347\272\277\346\216\222\345\272\217.java" new file mode 100644 index 0000000..cabacab --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\345\260\206\347\237\251\351\230\265\346\214\211\345\257\271\350\247\222\347\272\277\346\216\222\345\272\217.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; + +/** + * 1329. 将矩阵按对角线排序 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 将矩阵按对角线排序 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] input1 = { { 3, 3, 1, 1 }, { 2, 2, 1, 2 }, { 1, 1, 1, 2 } }; + int[][] expected1 = { { 1, 1, 1, 1 }, { 1, 2, 2, 2 }, { 1, 2, 3, 3 } }; + int[][] output1 = s.diagonalSort(input1); + Assertions.assertArrayEquals(expected1, output1); + + int[][] input2 = { { 11, 25, 66, 1, 69, 7 }, { 23, 55, 17, 45, 15, 52 }, { 75, 31, 36, 44, 58, 8 }, + { 22, 27, 33, 25, 68, 4 }, { 84, 28, 14, 11, 5, 50 } }; + int[][] expected2 = { { 5, 17, 4, 1, 52, 7 }, { 11, 11, 25, 45, 8, 69 }, { 14, 23, 25, 44, 58, 15 }, + { 22, 27, 31, 36, 50, 66 }, { 84, 28, 75, 33, 55, 68 } }; + int[][] output2 = s.diagonalSort(input2); + Assertions.assertArrayEquals(expected2, output2); + } + + static class Solution { + + public int[][] diagonalSort(int[][] mat) { + + int m = mat.length, n = mat[0].length; + + // 在同一个对角线上的元素,其横纵坐标之差是相同的 + // 存储所有对角线的元素列表,利用 PriorityQueue 自动对对角线元素排序 + Map> map = new HashMap<>(); + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + // 横纵坐标之差可以作为一条对角线的 ID + int diff = i - j; + if (!map.containsKey(diff)) { + map.put(diff, new PriorityQueue<>(Comparator.comparingInt(a -> mat[a[0]][a[1]]))); + } + map.get(diff).add(new int[] { i, j }); + } + } + + // 把排序结果回填二维矩阵 + int[][] res = new int[m][n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + int diff = i - j; + PriorityQueue queue = map.get(diff); + int[] point = queue.poll(); + res[i][j] = mat[point[0]][point[1]]; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" new file mode 100644 index 0000000..008fb47 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 14. 最长公共前缀 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 最长公共前缀 { + + public static void main(String[] args) { + + Solution s = new Solution(); + String[] input1 = { "flower", "flow", "flight" }; + String expect1 = "fl"; + String output1 = s.longestCommonPrefix(input1); + Assertions.assertEquals(expect1, output1); + + String[] input2 = { "dog", "racecar", "car" }; + String expect2 = ""; + String output2 = s.longestCommonPrefix(input2); + Assertions.assertEquals(expect2, output2); + } + + static class Solution { + + public String longestCommonPrefix(String[] strs) { + int m = strs.length; + // 以第一行的列数为基准 + int n = strs[0].length(); + for (int col = 0; col < n; col++) { + for (int row = 1; row < m; row++) { + String cur = strs[row], prev = strs[row - 1]; + // 判断每个字符串的 col 索引是否都相同 + if (col >= cur.length() || col >= prev.length() || + cur.charAt(col) != prev.charAt(col)) { + // 发现不匹配的字符,只有 strs[row][0..col-1] 是公共前缀 + return strs[row].substring(0, col); + } + } + } + return strs[0]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.java" new file mode 100644 index 0000000..c42a437 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\344\270\262.java" @@ -0,0 +1,84 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 5. 最长回文子串 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长回文子串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("bab", s.longestPalindrome("babad")); + Assertions.assertEquals("bb", s.longestPalindrome("cbbd")); + Assertions.assertEquals("a", s.longestPalindrome("a")); + Assertions.assertEquals("bb", s.longestPalindrome("bb")); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals("aba", s2.longestPalindrome("babad")); + Assertions.assertEquals("bb", s2.longestPalindrome("cbbd")); + Assertions.assertEquals("a", s2.longestPalindrome("a")); + Assertions.assertEquals("bb", s2.longestPalindrome("bb")); + } + + /** + * 双指针判断回文串 + 暴力解决,时间复杂度 o(n^2) + */ + static class Solution { + + public String longestPalindrome(String s) { + String res = s.substring(0, 1); + for (int i = 0; i < s.length(); i++) { + for (int j = i + 1; j < s.length(); j++) { + if (isPalindrome(s, i, j)) { + int len = j - i + 1; + if (len > res.length()) { + res = s.substring(i, j + 1); + } + } + } + } + return res; + } + + public boolean isPalindrome(String s, int left, int right) { + while (left < right) { + if (s.charAt(left) != s.charAt(right)) { + return false; + } + left++; + right--; + } + return true; + } + + } + + static class Solution2 { + + public String longestPalindrome(String s) { + String res = ""; + for (int i = 0; i < s.length(); i++) { + String s1 = palindrome(s, i, i); + String s2 = palindrome(s, i, i + 1); + res = res.length() > s1.length() ? res : s1; + res = res.length() > s2.length() ? res : s2; + } + return res; + } + + public String palindrome(String s, int l, int r) { + while (l >= 0 && r < s.length() + && s.charAt(l) == s.charAt(r)) { + l--; + r++; + } + return s.substring(l + 1, r); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.java" new file mode 100644 index 0000000..e9324ca --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\346\225\260\347\273\204\347\232\204\345\271\263\346\226\271.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 977. 有序数组的平方 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 有序数组的平方 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] input1 = { -4, -1, 0, 3, 10 }; + int[] expect1 = { 0, 1, 9, 16, 100 }; + int[] output1 = s.sortedSquares(input1); + Assertions.assertArrayEquals(expect1, output1); + + int[] input2 = { -7, -3, 2, 3, 11 }; + int[] expect2 = { 4, 9, 9, 49, 121 }; + int[] output2 = s.sortedSquares(input2); + Assertions.assertArrayEquals(expect2, output2); + } + + public static class Solution { + + public int[] sortedSquares(int[] nums) { + int p = nums.length - 1; + int i = 0, j = nums.length - 1; + int[] res = new int[nums.length]; + while (i <= j) { + if (Math.abs(nums[i]) > Math.abs(nums[j])) { + res[p] = nums[i] * nums[i]; + i++; + } else { + res[p] = nums[j] * nums[j]; + j--; + } + p--; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" new file mode 100644 index 0000000..59232f3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\234\211\345\272\217\347\237\251\351\230\265\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.PriorityQueue; + +/** + * 378. 有序矩阵中第 K 小的元素 + * + * @author Zhang Peng + * @date 2025-01-21 + */ +public class 有序矩阵中第K小的元素 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] matrix = { { 1, 5, 9 }, { 10, 11, 13 }, { 12, 13, 15 } }; + Assertions.assertEquals(13, s.kthSmallest(matrix, 8)); + + int[][] matrix2 = { { -5 } }; + Assertions.assertEquals(-5, s.kthSmallest(matrix2, 1)); + + int[][] matrix3 = { { 1, 2 }, { 1, 3 } }; + Assertions.assertEquals(1, s.kthSmallest(matrix3, 2)); + + int[][] matrix4 = { { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 } }; + Assertions.assertEquals(3, s.kthSmallest(matrix4, 8)); + } + + static class Solution { + + public int kthSmallest(int[][] matrix, int k) { + // 存储二元组 (matrix[i][j], i, j) + // i, j 记录当前元素的索引位置,用于生成下一个节点 + PriorityQueue pq = new PriorityQueue<>((a, b) -> { + // 按照元素大小升序排序 + return a[0] - b[0]; + }); + + // 初始化优先级队列,把每一行的第一个元素装进去 + for (int i = 0; i < matrix.length; i++) { + pq.offer(new int[] { matrix[i][0], i, 0 }); + } + + int res = -1; + while (!pq.isEmpty() && k > 0) { + int[] cur = pq.poll(); + res = cur[0]; + k--; + + // 链表中的下一个节点加入优先级队列 + int i = cur[1], j = cur[2]; + if (j + 1 < matrix[i].length) { + pq.add(new int[] { matrix[i][j + 1], i, j + 1 }); + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204K\345\257\271\346\225\260\345\255\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204K\345\257\271\346\225\260\345\255\227.java" new file mode 100644 index 0000000..89cfd6a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\345\222\214\346\234\200\345\260\217\347\232\204K\345\257\271\346\225\260\345\255\227.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import cn.hutool.json.JSONUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; + +/** + * 373. 查找和最小的 K 对数字 + * + * @author Zhang Peng + * @date 2025-01-21 + */ +public class 查找和最小的K对数字 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> expectList1 = new ArrayList<>(); + expectList1.add(Arrays.asList(1, 2)); + expectList1.add(Arrays.asList(1, 4)); + expectList1.add(Arrays.asList(1, 6)); + List> list1 = s.kSmallestPairs(new int[] { 1, 7, 11 }, new int[] { 2, 4, 6 }, 3); + System.out.println(JSONUtil.toJsonStr(list1)); + + List> list2 = s.kSmallestPairs(new int[] { 1, 1, 2 }, new int[] { 1, 2, 3 }, 2); + System.out.println(JSONUtil.toJsonStr(list2)); + } + + static class Solution { + + public List> kSmallestPairs(int[] nums1, int[] nums2, int k) { + PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a[0] + a[1]))); + for (int i = 0; i < nums1.length; i++) { + for (int j = 0; j < nums2.length; j++) { + queue.offer(new int[] { nums1[i], nums2[j] }); + } + } + + List> res = new ArrayList<>(); + for (int i = 0; i < k; i++) { + int[] element = queue.poll(); + res.add(Arrays.asList(element[0], element[1])); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\346\200\273\344\273\267\346\240\274\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\344\270\244\344\270\252\345\225\206\345\223\201.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\346\200\273\344\273\267\346\240\274\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\344\270\244\344\270\252\345\225\206\345\223\201.java" new file mode 100644 index 0000000..626bd5b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\346\237\245\346\211\276\346\200\273\344\273\267\346\240\274\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\344\270\244\344\270\252\345\225\206\345\223\201.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.Set; + +/** + * LCR 179. 查找总价格为目标值的两个商品 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 查找总价格为目标值的两个商品 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 3, 15 }, s.twoSum(new int[] { 3, 9, 12, 15 }, 18)); + Assertions.assertArrayEquals(new int[] { 27, 34 }, s.twoSum(new int[] { 8, 21, 27, 34, 52, 66 }, 61)); + } + + static class Solution { + + public int[] twoSum(int[] nums, int target) { + Set set = new HashSet<>(); + for (int num : nums) { + int diff = target - num; + if (set.contains(diff)) { + return new int[] { num, diff }; + } else { + set.add(num); + } + } + return new int[0]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\345\212\250\351\233\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\345\212\250\351\233\266.java" new file mode 100644 index 0000000..b682999 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\345\212\250\351\233\266.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 283. 移动零 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 移动零 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] arr1 = { 0, 1, 0, 3, 12 }; + s.moveZeroes(arr1); + Assertions.assertArrayEquals(new int[] { 1, 3, 12, 0, 0 }, arr1); + + int[] arr2 = { 0, 0, 1 }; + s.moveZeroes(arr2); + Assertions.assertArrayEquals(new int[] { 1, 0, 0 }, arr2); + + int[] arr3 = { 0 }; + s.moveZeroes(arr3); + Assertions.assertArrayEquals(new int[] { 0 }, arr3); + } + + public static class Solution { + + public void moveZeroes(int[] nums) { + // slow 指针维护所有不为 0 的元素 + int slow = 0, fast = 0; + while (fast < nums.length) { + if (nums[fast] != 0) { + nums[slow] = nums[fast]; + slow++; + } + fast++; + } + // 后续补零 + for (int i = slow; i < nums.length; i++) { + nums[i] = 0; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\351\231\244\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\351\231\244\345\205\203\347\264\240.java" new file mode 100644 index 0000000..52992a1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\347\247\273\351\231\244\345\205\203\347\264\240.java" @@ -0,0 +1,43 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 27. 移除元素 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 移除元素 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] arr1 = { 3, 2, 2, 3 }; + Assertions.assertEquals(2, s.removeElement(arr1, 3)); + + int[] arr2 = { 0, 1, 2, 2, 3, 0, 4, 2 }; + Assertions.assertEquals(5, s.removeElement(arr2, 2)); + + int[] arr3 = { 1 }; + Assertions.assertEquals(0, s.removeElement(arr3, 1)); + } + + static class Solution { + + public int removeElement(int[] nums, int val) { + int slow = 0, fast = 0; + while (fast < nums.length) { + if (nums[fast] != val) { + nums[slow] = nums[fast]; + slow++; + } + fast++; + } + return slow; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\350\275\254\347\275\256\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\350\275\254\347\275\256\347\237\251\351\230\265.java" new file mode 100644 index 0000000..92104fe --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\350\275\254\347\275\256\347\237\251\351\230\265.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 1329. 将矩阵按对角线排序 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 转置矩阵 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input1 = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; + int[][] expect1 = { { 1, 4, 7 }, { 2, 5, 8 }, { 3, 6, 9 } }; + int[][] output1 = s.transpose(input1); + Assertions.assertArrayEquals(expect1, output1); + + int[][] input2 = { { 1, 2, 3 }, { 4, 5, 6 } }; + int[][] expect2 = { { 1, 4 }, { 2, 5 }, { 3, 6 } }; + int[][] output2 = s.transpose(input2); + Assertions.assertArrayEquals(expect2, output2); + } + + public static class Solution { + + public int[][] transpose(int[][] matrix) { + int m = matrix.length, n = matrix[0].length; + int[][] res = new int[n][m]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + res[j][i] = matrix[i][j]; + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\242\234\350\211\262\345\210\206\347\261\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\242\234\350\211\262\345\210\206\347\261\273.java" new file mode 100644 index 0000000..d8d6d83 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\242\234\350\211\262\345\210\206\347\261\273.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import cn.hutool.json.JSONUtil; +import org.junit.jupiter.api.Assertions; + +/** + * 75. 颜色分类 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 颜色分类 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[] nums1 = { 2, 0, 2, 1, 1, 0 }; + s.sortColors(nums1); + Assertions.assertArrayEquals(new int[] { 0, 0, 1, 1, 2, 2 }, nums1); + + int[] nums2 = { 2, 0, 1 }; + s.sortColors(nums2); + Assertions.assertArrayEquals(new int[] { 0, 1, 2 }, nums2); + } + + static class Solution { + + public void sortColors(int[] nums) { + moveToTail(nums, 1); + System.out.println("nums = " + JSONUtil.toJsonStr(nums)); + moveToTail(nums, 2); + System.out.println("nums = " + JSONUtil.toJsonStr(nums)); + } + + public void moveToTail(int[] nums, int val) { + if (nums == null || nums.length == 0) { return; } + int slow = 0, fast = 0; + while (fast < nums.length) { + if (nums[fast] != val) { + nums[slow] = nums[fast]; + slow++; + } + fast++; + } + for (int i = slow; i < nums.length; i++) { + nums[i] = val; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.java" new file mode 100644 index 0000000..4c1597e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/two_pointer/\351\252\214\350\257\201\345\233\236\346\226\207\344\270\262.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.array.two_pointer; + +import org.junit.jupiter.api.Assertions; + +/** + * 125. 验证回文串 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 验证回文串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isPalindrome("A man, a plan, a canal: Panama")); + Assertions.assertFalse(s.isPalindrome("race a car")); + Assertions.assertTrue(s.isPalindrome(" ")); + Assertions.assertTrue(s.isPalindrome("ab_a")); + + Solution2 s2 = new Solution2(); + Assertions.assertTrue(s2.isPalindrome("A man, a plan, a canal: Panama")); + Assertions.assertFalse(s2.isPalindrome("race a car")); + Assertions.assertTrue(s2.isPalindrome(" ")); + Assertions.assertTrue(s2.isPalindrome("ab_a")); + } + + static class Solution { + + public boolean isPalindrome(String s) { + String format = s.replaceAll("[^a-zA-Z0-9]", "").toLowerCase(); + return isPalindrome(format, 0, format.length() - 1); + } + + public boolean isPalindrome(String s, int left, int right) { + while (left < right) { + if (s.charAt(left) != s.charAt(right)) { + return false; + } + left++; + right--; + } + return true; + } + + } + + static class Solution2 { + + public boolean isPalindrome(String s) { + int left = 0, right = s.length() - 1; + while (left < right) { + if (!Character.isLetterOrDigit(s.charAt(left))) { + left++; + continue; + } + if (!Character.isLetterOrDigit(s.charAt(right))) { + right--; + continue; + } + + char l = Character.toLowerCase(s.charAt(left)); + char r = Character.toLowerCase(s.charAt(right)); + if (l != r) { + return false; + } + left++; + right--; + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\344\271\230\347\247\257\345\260\217\344\272\216K\347\232\204\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\344\271\230\347\247\257\345\260\217\344\272\216K\347\232\204\345\255\220\346\225\260\347\273\204.java" new file mode 100644 index 0000000..6a6d857 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\344\271\230\347\247\257\345\260\217\344\272\216K\347\232\204\345\255\220\346\225\260\347\273\204.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 713. 乘积小于 K 的子数组 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 乘积小于K的子数组 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(8, s.numSubarrayProductLessThanK(new int[] { 10, 5, 2, 6 }, 100)); + Assertions.assertEquals(0, s.numSubarrayProductLessThanK(new int[] { 1, 2, 3 }, 0)); + } + + static class Solution { + + public int numSubarrayProductLessThanK(int[] nums, int k) { + int left = 0, right = 0; + // 滑动窗口,初始化为乘法单位元 + int windowProduct = 1; + // 记录符合条件的子数组个数 + int count = 0; + + while (right < nums.length) { + // 扩大窗口,并更新窗口数据 + windowProduct = windowProduct * nums[right]; + right++; + + while (left < right && windowProduct >= k) { + // 缩小窗口,并更新窗口数据 + windowProduct = windowProduct / nums[left]; + left++; + } + // 现在必然是一个合法的窗口,但注意思考这个窗口中的子数组个数怎么计算: + // 比方说 left = 1, right = 4 划定了 [1, 2, 3] 这个窗口(right 是开区间) + // 但不止 [left..right] 是合法的子数组,[left+1..right], [left+2..right] 等都是合法子数组 + // 所以我们需要把 [3], [2,3], [1,2,3] 这 right - left 个子数组都加上 + count += right - left; + } + + return count; + } + + } + +} \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.java" new file mode 100644 index 0000000..22f6ea9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 567. 字符串的排列 + * + * @author Zhang Peng + * @since 2025-08-06 + */ +public class 字符串的排列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.checkInclusion("ab", "eidbaooo")); + Assertions.assertFalse(s.checkInclusion("ab", "eidboaoo")); + } + + static class Solution { + + public boolean checkInclusion(String t, String s) { + Map need = new HashMap<>(); + Map window = new HashMap<>(); + for (char c : t.toCharArray()) need.put(c, need.getOrDefault(c, 0) + 1); + + int left = 0, right = 0; + int valid = 0; + while (right < s.length()) { + char c = s.charAt(right); + right++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(c)) { + window.put(c, window.getOrDefault(c, 0) + 1); + if (window.get(c).equals(need.get(c))) { valid++; } + } + + // 判断左侧窗口是否要收缩 + while (right - left >= t.length()) { + // 在这里判断是否找到了合法的子串 + if (valid == need.size()) { return true; } + char d = s.charAt(left); + left++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(d)) { + if (window.get(d).equals(need.get(d))) { valid--; } + window.put(d, window.get(d) - 1); + } + } + } + // 未找到符合条件的子串 + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" new file mode 100644 index 0000000..8bf319d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.Set; + +/** + * 217. 存在重复元素 + * + * @author Zhang Peng + * @since 2020-06-05 + */ +public class 存在重复元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.containsDuplicate(new int[] { 1, 2, 3, 1 })); + Assertions.assertFalse(s.containsDuplicate(new int[] { 1, 2, 3, 4 })); + Assertions.assertTrue(s.containsDuplicate(new int[] { 1, 1, 1, 3, 3, 4, 3, 2, 4, 2 })); + } + + static class Solution { + + public boolean containsDuplicate(int[] nums) { + Set set = new HashSet<>(); + for (int num : nums) { + if (set.contains(num)) { + return true; + } + set.add(num); + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2402.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2402.java" new file mode 100644 index 0000000..2e6b279 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2402.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; + +/** + * 219. 存在重复元素 II + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 存在重复元素2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.containsNearbyDuplicate(new int[] { 1, 2, 3, 1 }, 3)); + Assertions.assertTrue(s.containsNearbyDuplicate(new int[] { 1, 0, 1, 1 }, 1)); + Assertions.assertFalse(s.containsNearbyDuplicate(new int[] { 1, 2, 3, 1, 2, 3 }, 2)); + Assertions.assertTrue(s.containsNearbyDuplicate(new int[] { 99, 99 }, 2)); + } + + static class Solution { + + public boolean containsNearbyDuplicate(int[] nums, int k) { + + // base case + if (nums == null || nums.length < 2) { return false; } + + int left = 0, right = 0; + HashSet window = new HashSet<>(); + // 滑动窗口算法框架,维护一个大小为 k 的窗口 + while (right < nums.length) { + // 扩大窗口 + if (window.contains(nums[right])) { return true; } + window.add(nums[right]); + right++; + + if (right - left > k) { + // 当窗口的大小大于 k 时,缩小窗口 + window.remove(nums[left]); + left++; + } + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2403.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2403.java" new file mode 100644 index 0000000..8265957 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\2403.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.TreeSet; + +/** + * 220. 存在重复元素 III + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 存在重复元素3 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.containsNearbyAlmostDuplicate(new int[] { 1, 2, 3, 1 }, 3, 0)); + Assertions.assertFalse(s.containsNearbyAlmostDuplicate(new int[] { 1, 5, 9, 1, 5, 9 }, 2, 3)); + Assertions.assertTrue(s.containsNearbyAlmostDuplicate(new int[] { 1, 2, 2, 3, 4, 5 }, 3, 0)); + } + + static class Solution { + + public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) { + TreeSet window = new TreeSet<>(); + int left = 0, right = 0; + while (right < nums.length) { + // 为了防止 i == j,所以在扩大窗口之前先判断是否有符合题意的索引对 (i, j) + // 查找略大于 nums[right] 的那个元素 + Integer ceiling = window.ceiling(nums[right]); + if (ceiling != null && (long) ceiling - nums[right] <= t) { + return true; + } + // 查找略小于 nums[right] 的那个元素 + Integer floor = window.floor(nums[right]); + if (floor != null && (long) nums[right] - floor <= t) { + return true; + } + + // 扩大窗口 + window.add(nums[right]); + right++; + + if (right - left > k) { + // 缩小窗口 + window.remove(nums[left]); + left++; + } + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\260\206x\345\207\217\345\210\2600\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\260\206x\345\207\217\345\210\2600\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.java" new file mode 100644 index 0000000..d39d1c8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\345\260\206x\345\207\217\345\210\2600\347\232\204\346\234\200\345\260\217\346\223\215\344\275\234\346\225\260.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 1658. 将 x 减到 0 的最小操作数 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 将x减到0的最小操作数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.minOperations(new int[] { 1, 1, 4, 2, 3 }, 5)); + Assertions.assertEquals(-1, s.minOperations(new int[] { 5, 6, 7, 8, 9 }, 4)); + Assertions.assertEquals(5, s.minOperations(new int[] { 3, 2, 20, 1, 1, 3 }, 10)); + Assertions.assertEquals(16, s.minOperations(new int[] { 8828, 9581, 49, 9818, 9974, 9869, 9991, + 10000, 10000, 10000, 9999, 9993, 9904, 8819, 1231, 6309 }, 134365)); + } + + static class Solution { + + // 【思路】 + // 从边缘删除掉和为 x 的元素,那剩下来的是什么?剩下来的是不是就是 nums 中的一个子数组? + // 让你尽可能少地从边缘删除元素说明什么?是不是就是说剩下来的这个子数组大小尽可能的大? + // 所以,这道题等价于让你寻找 nums 中元素和为 sum(nums) - x 的最长子数组。 + + // 1、当窗口内元素之和小于目标和 target 时,扩大窗口,让窗口内元素和增加。 + // 2、当窗口内元素之和大于目标和 target 时,缩小窗口,让窗口内元素和减小。 + // 3、当窗口内元素之和等于目标和 target 时,找到一个符合条件的子数组,我们想找的是最长的子数组长度。 + public int minOperations(int[] nums, int x) { + int n = nums.length, sum = 0; + for (int num : nums) { sum += num; } + // 滑动窗口需要寻找的子数组目标和 + int target = sum - x; + + int left = 0, right = 0; + // 记录窗口内所有元素和 + int windowSum = 0; + // 记录目标子数组的最大长度 + int maxLen = Integer.MIN_VALUE; + // 开始执行滑动窗口框架 + while (right < nums.length) { + // 扩大窗口 + windowSum += nums[right]; + right++; + + while (windowSum > target && left < right) { + // 缩小窗口 + windowSum -= nums[left]; + left++; + } + // 寻找目标子数组 + if (windowSum == target) { + maxLen = Math.max(maxLen, right - left); + } + } + // 目标子数组的最大长度可以推导出需要删除的字符数量 + return maxLen == Integer.MIN_VALUE ? -1 : n - maxLen; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.java" new file mode 100644 index 0000000..2a6bb20 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\211\276\345\210\260\345\255\227\347\254\246\344\270\262\344\270\255\346\211\200\346\234\211\345\255\227\346\257\215\345\274\202\344\275\215\350\257\215.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 438. 找到字符串中所有字母异位词 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 找到字符串中所有字母异位词 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 0, 6 }, s.findAnagrams("cbaebabacd", "abc").toArray()); + Assertions.assertArrayEquals(new Integer[] { 0, 1, 2 }, s.findAnagrams("abab", "ab").toArray()); + } + + static class Solution { + + public List findAnagrams(String s, String t) { + Map need = new HashMap<>(); + Map window = new HashMap<>(); + for (char c : t.toCharArray()) { + need.put(c, need.getOrDefault(c, 0) + 1); + } + + int left = 0, right = 0; + int valid = 0; + // 记录结果 + List res = new ArrayList<>(); + while (right < s.length()) { + char c = s.charAt(right); + right++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(c)) { + window.put(c, window.getOrDefault(c, 0) + 1); + if (window.get(c).equals(need.get(c))) { + valid++; + } + } + // 判断左侧窗口是否要收缩 + while (right - left >= t.length()) { + // 当窗口符合条件时,把起始索引加入 res + if (valid == need.size()) { + res.add(left); + } + char d = s.charAt(left); + left++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(d)) { + if (window.get(d).equals(need.get(d))) { + valid--; + } + window.put(d, window.get(d) - 1); + } + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" new file mode 100644 index 0000000..52f0955 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\227\240\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 3. 无重复字符的最长子串 + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 无重复字符的最长子串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.lengthOfLongestSubstring("abcabcbb")); + Assertions.assertEquals(1, s.lengthOfLongestSubstring("bbbbb")); + Assertions.assertEquals(3, s.lengthOfLongestSubstring("pwwkew")); + Assertions.assertEquals(2, s.lengthOfLongestSubstring("aab")); + } + + static class Solution { + + public int lengthOfLongestSubstring(String s) { + Map window = new HashMap<>(); + + int left = 0, right = 0; + // 记录结果 + int res = 0; + while (right < s.length()) { + char c = s.charAt(right); + right++; + // 进行窗口内数据的一系列更新 + window.put(c, window.getOrDefault(c, 0) + 1); + // 判断左侧窗口是否要收缩 + while (window.get(c) > 1) { + char d = s.charAt(left); + left++; + // 进行窗口内数据的一系列更新 + window.put(d, window.get(d) - 1); + } + // 在这里更新答案 + res = Math.max(res, right - left); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.java" new file mode 100644 index 0000000..58f6ba0 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246.java" @@ -0,0 +1,56 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 424. 替换后的最长重复字符 + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 替换后的最长重复字符 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.characterReplacement("ABAB", 2)); + Assertions.assertEquals(4, s.characterReplacement("AABABBA", 1)); + Assertions.assertEquals(4, s.characterReplacement("AAAA", 2)); + } + + static class Solution { + + public int characterReplacement(String s, int k) { + int left = 0, right = 0; + // 统计窗口中每个字符的出现次数 + int[] windowCharCount = new int[26]; + // 记录窗口中字符的最多重复次数 + // 记录这个值的意义在于,最划算的替换方法肯定是把其他字符替换成出现次数最多的那个字符 + int windowMaxCount = 0; + // 记录结果长度 + int res = 0; + + // 开始滑动窗口模板 + while (right < s.length()) { + // 扩大窗口 + int c = s.charAt(right) - 'A'; + windowCharCount[c]++; + windowMaxCount = Math.max(windowMaxCount, windowCharCount[c]); + right++; + + // 这个 while 换成 if 也可以 + while (right - left - windowMaxCount > k) { + // 杂牌字符数量 right - left - windowMaxCount 多于 k + // 此时,k 次替换已经无法把窗口内的字符都替换成相同字符了 + // 必须缩小窗口 + windowCharCount[s.charAt(left) - 'A']--; + left++; + } + // 经过收缩后,此时一定是一个合法的窗口 + res = Math.max(res, right - left); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\2603.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\2603.java" new file mode 100644 index 0000000..531e688 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\2603.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 1004. 最大连续1的个数 III + * + * @author Zhang Peng + * @date 2025-10-14 + */ +public class 最大连续1的个数3 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.longestOnes(new int[] { 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, 2)); + Assertions.assertEquals(10, + s.longestOnes(new int[] { 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, 3)); + } + + static class Solution { + + public int longestOnes(int[] nums, int k) { + int cnt = 0, len = 0; + int left = 0, right = 0; + while (right < nums.length) { + if (nums[right] == 0) { cnt++; } + right++; + + while (cnt > k) { + if (nums[left] == 0) { cnt--; } + left++; + } + len = Math.max(len, right - left); + } + return len; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.java" new file mode 100644 index 0000000..81b42a2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.array.window; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 76. 最小覆盖子串 + * + * @author Zhang Peng + * @date 2025-01-10 + */ +@Slf4j +public class 最小覆盖子串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("BANC", s.minWindow("ADOBECODEBANC", "ABC")); + Assertions.assertEquals("a", s.minWindow("a", "a")); + Assertions.assertEquals("", s.minWindow("a", "aa")); + } + + static class Solution { + + public String minWindow(String s, String t) { + Map need = new HashMap<>(); + Map window = new HashMap<>(); + for (char c : t.toCharArray()) { + need.put(c, need.getOrDefault(c, 0) + 1); + } + + int valid = 0; + int left = 0, right = 0; + // 记录最小覆盖子串的起始索引及长度 + int start = 0, len = Integer.MAX_VALUE; + while (right < s.length()) { + // c 是将移入窗口的字符 + char c = s.charAt(right); + // 扩大窗口 + right++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(c)) { + window.put(c, window.getOrDefault(c, 0) + 1); + if (window.get(c).equals(need.get(c))) { valid++; } + } + + // 判断左侧窗口是否要收缩 + while (valid == need.size()) { + + // 在这里更新最小覆盖子串 + if (right - left < len) { + start = left; + len = right - left; + } + // d 是将移出窗口的字符 + char d = s.charAt(left); + // 缩小窗口 + left++; + // 进行窗口内数据的一系列更新 + if (need.containsKey(d)) { + if (window.get(d).equals(need.get(d))) { valid--; } + window.put(d, window.get(d) - 1); + } + } + } + // 返回最小覆盖子串 + return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\350\207\263\345\260\221\346\234\211K\344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\350\207\263\345\260\221\346\234\211K\344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" new file mode 100644 index 0000000..5a8994b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\350\207\263\345\260\221\346\234\211K\344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262.java" @@ -0,0 +1,86 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 395. 至少有 K + * 个重复字符的最长子串 + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 至少有K个重复字符的最长子串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.longestSubstring("aaabb", 3)); + Assertions.assertEquals(5, s.longestSubstring("ababbc", 2)); + Assertions.assertEquals(6, s.longestSubstring("aaabbb", 3)); + } + + public static class Solution { + + public int longestSubstring(String s, int k) { + int len = 0; + for (int i = 1; i <= 26; i++) { + // 限制窗口中只能有 i 种不同字符 + len = Math.max(len, longestKLetterSubstring(s, k, i)); + } + return len; + } + + // 寻找 s 中含有 count 种字符,且每种字符出现次数都大于 k 的子串 + public int longestKLetterSubstring(String s, int k, int count) { + + // 记录答案 + int res = 0; + // 快慢指针维护滑动窗口,左闭右开区间 + int left = 0, right = 0; + // 题目说 s 中只有小写字母,所以用大小 26 的数组记录窗口中字符出现的次数 + int[] windowCount = new int[26]; + // 记录窗口中存在几种不同的字符(字符种类) + int windowUniqueCount = 0; + // 记录窗口中有几种字符的出现次数达标(大于等于 k) + int windowValidCount = 0; + // 滑动窗口代码模板 + while (right < s.length()) { + // 移入字符,扩大窗口 + int c = s.charAt(right) - 'a'; + if (windowCount[c] == 0) { + // 窗口中新增了一种字符 + windowUniqueCount++; + } + windowCount[c]++; + if (windowCount[c] == k) { + // 窗口中新增了一种达标的字符 + windowValidCount++; + } + right++; + + // 当窗口中字符种类大于 count 时,缩小窗口 + while (windowUniqueCount > count) { + // 移出字符,缩小窗口 + int d = s.charAt(left) - 'a'; + if (windowCount[d] == k) { + // 窗口中减少了一种达标的字符 + windowValidCount--; + } + windowCount[d]--; + if (windowCount[d] == 0) { + // 窗口中减少了一种字符 + windowUniqueCount--; + } + left++; + } + + // 当窗口中字符种类为 count 且每个字符出现次数都满足 k 时,更新答案 + if (windowValidCount == count) { + res = Math.max(res, right - left); + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" new file mode 100644 index 0000000..b636134 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/window/\351\225\277\345\272\246\346\234\200\345\260\217\347\232\204\345\255\220\346\225\260\347\273\204.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.array.window; + +import org.junit.jupiter.api.Assertions; + +/** + * 209. 长度最小的子数组 + * + * @author Zhang Peng + * @date 2025-10-15 + */ +public class 长度最小的子数组 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.minSubArrayLen(7, new int[] { 2, 3, 1, 2, 4, 3 })); + Assertions.assertEquals(1, s.minSubArrayLen(4, new int[] { 1, 4, 4 })); + Assertions.assertEquals(0, s.minSubArrayLen(11, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 })); + } + + public static class Solution { + + public int minSubArrayLen(int target, int[] nums) { + int left = 0, right = 0; + // 维护窗口内元素之和 + int windowSum = 0; + int res = Integer.MAX_VALUE; + + while (right < nums.length) { + // 扩大窗口 + windowSum += nums[right]; + right++; + while (windowSum >= target && left < right) { + // 已经达到 target,缩小窗口,同时更新答案 + res = Math.min(res, right - left); + windowSum -= nums[left]; + left++; + } + } + return res == Integer.MAX_VALUE ? 0 : res; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/package-info.java new file mode 100644 index 0000000..04a66a6 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/package-info.java @@ -0,0 +1,7 @@ +/** + * 通过 BFS 解最短路径类型问题 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +package io.github.dunwu.algorithm.bfs; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/template/BFS\347\256\227\346\263\225\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/template/BFS\347\256\227\346\263\225\346\250\241\346\235\277.java" new file mode 100644 index 0000000..0448d03 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/template/BFS\347\256\227\346\263\225\346\250\241\346\235\277.java" @@ -0,0 +1,51 @@ +package io.github.dunwu.algorithm.bfs.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * BFS 算法模板 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +public class BFS算法模板 { + + private Graph graph; + + // 从 s 开始 BFS 遍历图的所有节点,且记录遍历的步数 + // 当走到目标节点 target 时,返回步数 + int bfs(int s, int target) { + boolean[] visited = new boolean[graph.size()]; + Queue q = new LinkedList<>(); + q.offer(s); + visited[s] = true; + // 记录从 s 开始走到当前节点的步数 + int step = 0; + while (!q.isEmpty()) { + int sz = q.size(); + for (int i = 0; i < sz; i++) { + int cur = q.poll(); + System.out.println("visit " + cur + " at step " + step); + // 判断是否到达终点 + if (cur == target) { + return step; + } + // 将邻居节点加入队列,向四周扩散搜索 + for (Edge e : graph.neighbors(cur)) { + if (!visited[e.to]) { + q.offer(e.to); + visited[e.to] = true; + } + } + } + step++; + } + // 如果走到这里,说明在图中没有找到目标节点 + return -1; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.java" new file mode 100644 index 0000000..ca60ee9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\344\272\214\350\277\233\345\210\266\347\237\251\351\230\265\344\270\255\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1091. 二进制矩阵中的最短路径 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +public class 二进制矩阵中的最短路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.shortestPathBinaryMatrix(new int[][] { { 0, 1 }, { 1, 0 } })); + Assertions.assertEquals(4, s.shortestPathBinaryMatrix(new int[][] { { 0, 0, 0 }, { 1, 1, 0 }, { 1, 1, 0 } })); + Assertions.assertEquals(-1, s.shortestPathBinaryMatrix(new int[][] { { 1, 0, 0 }, { 1, 1, 0 }, { 1, 1, 0 } })); + } + + static class Solution { + + // 八个方向偏移量(上、下、左、右、左上、右下、左下、右上) + private final int[][] directions = { + { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }, + { -1, -1 }, { 1, 1 }, { -1, 1 }, { 1, -1 } + }; + + public int shortestPathBinaryMatrix(int[][] grid) { + + int m = grid.length, n = grid[0].length; + if (grid[0][0] == 1 || grid[m - 1][n - 1] == 1) { + return -1; + } + + // 需要记录走过的路径,避免死循环 + boolean[][] visited = new boolean[m][n]; + LinkedList queue = new LinkedList<>(); + + // 初始化队列,从 (0, 0) 出发 + visited[0][0] = true; + queue.offer(new int[] { 0, 0 }); + + int step = 1; + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + int[] cur = queue.poll(); + int x = cur[0], y = cur[1]; + if (grid[x][y] != 0) { return -1; } + // 到达底部,返回步骤数 + if (x == m - 1 && y == n - 1) { return step; } + + for (int[] d : directions) { + int nextX = x + d[0], nextY = y + d[1]; + if (nextX < 0 || nextX >= m || nextY < 0 || nextY >= n) { continue; } + if (visited[nextX][nextY] || grid[nextX][nextY] != 0) { continue; } + visited[nextX][nextY] = true; + queue.offer(new int[] { nextX, nextY }); + } + } + step++; + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" new file mode 100644 index 0000000..77990db --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" @@ -0,0 +1,68 @@ +package io.github.dunwu.algorithm.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 919. 完全二叉树插入器 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 完全二叉树插入器 { + + public static void main(String[] args) { + CBTInserter c = new CBTInserter(TreeNode.buildTree(1, 2)); + Assertions.assertEquals(1, c.insert(3)); + Assertions.assertEquals(2, c.insert(4)); + Assertions.assertEquals(TreeNode.buildTree(1, 2, 3, 4), c.get_root()); + } + + static class CBTInserter { + + private final TreeNode root; + // 这个队列只记录完全二叉树底部可以进行插入的节点 + private final LinkedList queue; + + public CBTInserter(TreeNode root) { + this.root = root; + this.queue = new LinkedList<>(); + LinkedList tmp = new LinkedList<>(); + tmp.offer(root); + while (!tmp.isEmpty()) { + int size = tmp.size(); + for (int i = 0; i < size; i++) { + TreeNode node = tmp.poll(); + if (node == null) { continue; } + if (node.left != null) { tmp.offer(node.left); } + if (node.right != null) { tmp.offer(node.right); } + if (node.left == null || node.right == null) { + // 找到完全二叉树底部可以进行插入的节点 + queue.offer(node); + } + } + } + } + + public int insert(int val) { + TreeNode node = new TreeNode(val); + TreeNode cur = queue.peek(); + queue.offer(node); + if (cur.left == null) { + cur.left = node; + } else if (cur.right == null) { + cur.right = node; + queue.poll(); + } + return cur.val; + } + + public TreeNode get_root() { + return this.root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\211\223\345\274\200\350\275\254\347\233\230\351\224\201.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\211\223\345\274\200\350\275\254\347\233\230\351\224\201.java" new file mode 100644 index 0000000..8a4c291 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\211\223\345\274\200\350\275\254\347\233\230\351\224\201.java" @@ -0,0 +1,100 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * 297. 二叉树的序列化与反序列化 + * + * @author Zhang Peng + * @date 2025-11-06 + */ +public class 打开转盘锁 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + String[] deadends = new String[] { "0201", "0101", "0102", "1212", "2002" }; + Assertions.assertEquals(6, s.openLock(deadends, "0202")); + + String[] deadends2 = new String[] { "8888" }; + Assertions.assertEquals(1, s.openLock(deadends2, "0009")); + + String[] deadends3 = new String[] { "8887", "8889", "8878", "8898", "8788", "8988", "7888", "9888" }; + Assertions.assertEquals(-1, s.openLock(deadends3, "8888")); + } + + static class Solution { + + public int openLock(String[] deadends, String target) { + int step = 0; + + Set blackSet = new HashSet<>(); + Collections.addAll(blackSet, deadends); + + if (blackSet.contains("0000")) { return -1; } + + Set visited = new HashSet<>(); + LinkedList queue = new LinkedList<>(); + visited.add("0000"); + queue.offer("0000"); + + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + String cur = queue.poll(); + if (cur.equals(target)) { + return step; + } + + for (String neighbour : neighbours(cur)) { + if (!visited.contains(neighbour) && !blackSet.contains(neighbour)) { + visited.add(neighbour); + queue.offer(neighbour); + } + } + } + step++; + } + return -1; + } + + public String plus(String s, int i) { + char[] ch = s.toCharArray(); + if (ch[i] == '9') { + ch[i] = '0'; + } else { + ch[i] += 1; + } + return new String(ch); + } + + public String minus(String s, int i) { + char[] ch = s.toCharArray(); + if (ch[i] == '0') { + ch[i] = '9'; + } else { + ch[i] -= 1; + } + return new String(ch); + } + + public List neighbours(String s) { + List neighbours = new ArrayList<>(); + for (int i = 0; i < s.length(); i++) { + neighbours.add(plus(s, i)); + neighbours.add(minus(s, i)); + } + return neighbours; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\234\200\345\260\217\345\237\272\345\233\240\345\217\230\345\214\226.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\234\200\345\260\217\345\237\272\345\233\240\345\217\230\345\214\226.java" new file mode 100644 index 0000000..c52c342 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\234\200\345\260\217\345\237\272\345\233\240\345\217\230\345\214\226.java" @@ -0,0 +1,82 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * 433. 最小基因变化 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 最小基因变化 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, + s.minMutation("AACCGGTT", "AACCGGTA", new String[] { "AACCGGTA" })); + Assertions.assertEquals(2, + s.minMutation("AACCGGTT", "AAACGGTA", new String[] { "AACCGGTA", "AACCGCTA", "AAACGGTA" })); + Assertions.assertEquals(3, + s.minMutation("AAAAACCC", "AACCCCCC", new String[] { "AAAACCCC", "AAACCCCC", "AACCCCCC" })); + Assertions.assertEquals(-1, + s.minMutation("AACCGGTT", "AACCGGTA", new String[] {})); + Assertions.assertEquals(-1, + s.minMutation("AAAAAAAA", "CCCCCCCC", + new String[] { "AAAAAAAA", "AAAAAAAC", "AAAAAACC", "AAAAACCC", "AAAACCCC", "AACACCCC", "ACCACCCC", + "ACCCCCCC", "CCCCCCCA" })); + } + + static class Solution { + + final char[] AGCT = new char[] { 'A', 'C', 'G', 'T' }; + + public int minMutation(String startGene, String endGene, String[] bank) { + if (startGene.equals(endGene)) { return 0; } + + int step = 0; + Set banks = new HashSet<>(Arrays.asList(bank)); + Set visited = new HashSet<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(startGene); + + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + String curGene = queue.poll(); + if (curGene.equals(endGene)) { return step; } + for (String newGene : neighbours(curGene)) { + if (!visited.contains(newGene) && banks.contains(newGene)) { + queue.offer(newGene); + visited.add(newGene); + } + } + } + step++; + } + return -1; + } + + // 当前基因的每个位置都可以变异为 A/G/C/T,穷举所有可能的结构 + public List neighbours(String gene) { + List res = new LinkedList<>(); + char[] ch = gene.toCharArray(); + for (int i = 0; i < ch.length; i++) { + char c = ch[i]; + for (char option : AGCT) { + ch[i] = option; + res.add(new String(ch)); + } + ch[i] = c; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\260\264\345\243\266\351\227\256\351\242\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\260\264\345\243\266\351\227\256\351\242\230.java" new file mode 100644 index 0000000..a70a284 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\346\260\264\345\243\266\351\227\256\351\242\230.java" @@ -0,0 +1,83 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 365. 水壶问题 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +public class 水壶问题 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canMeasureWater(3, 5, 4)); + Assertions.assertFalse(s.canMeasureWater(2, 6, 5)); + Assertions.assertTrue(s.canMeasureWater(1, 2, 3)); + } + + static class Solution { + + public boolean canMeasureWater(int x, int y, int t) { + // BFS 算法的队列 + LinkedList q = new LinkedList<>(); + // 用来记录已经遍历过的状态,把元组转化成数字方便存储哈希集合 + // 转化方式是 (x, y) -> (x * (y + 1) + y),和二维数组坐标转一维坐标是一样的原理 + // 因为水桶 2 的取值是 [0, y],所以需要额外加一,请类比二维数组坐标转一维坐标 + // 且考虑到题目输入的数据规模较大,相乘可能导致 int 溢出,所以使用 long 类型 + HashSet visited = new HashSet<>(); + // 添加初始状态,两个桶都没有水 + q.offer(new int[] { 0, 0 }); + visited.add((long) 0 * (0 + 1) + 0); + + while (!q.isEmpty()) { + int[] curState = q.poll(); + if (curState[0] == t || curState[1] == t + || curState[0] + curState[1] == t) { + // 如果任意一个桶的水量等于目标水量,就返回 true + return true; + } + // 计算出所有可能的下一个状态 + List nextStates = new LinkedList<>(); + // 把 1 桶灌满 + nextStates.add(new int[] { x, curState[1] }); + // 把 2 桶灌满 + nextStates.add(new int[] { curState[0], y }); + // 把 1 桶倒空 + nextStates.add(new int[] { 0, curState[1] }); + // 把 2 桶倒空 + nextStates.add(new int[] { curState[0], 0 }); + // 把 1 桶的水灌进 2 桶,直到 1 桶空了或者 2 桶满了 + nextStates.add(new int[] { + curState[0] - Math.min(curState[0], y - curState[1]), + curState[1] + Math.min(curState[0], y - curState[1]) + }); + // 把 2 桶的水灌进 1 桶,直到 2 桶空了或者 1 桶满了 + nextStates.add(new int[] { + curState[0] + Math.min(curState[1], x - curState[0]), + curState[1] - Math.min(curState[1], x - curState[0]) + }); + + // 把所有可能的下一个状态都放进队列里 + for (int[] nextState : nextStates) { + // 把二维坐标转化为数字,方便去重 + long hash = (long) nextState[0] * (y + 1) + nextState[1]; + if (visited.contains(hash)) { + // 如果这个状态之前遍历过,就跳过,避免队列永远不空陷入死循环 + continue; + } + q.offer(nextState); + visited.add(hash); + } + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\205\220\347\203\202\347\232\204\346\251\230\345\255\220.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\205\220\347\203\202\347\232\204\346\251\230\345\255\220.java" new file mode 100644 index 0000000..0b5751b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\205\220\347\203\202\347\232\204\346\251\230\345\255\220.java" @@ -0,0 +1,93 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 994. 腐烂的橘子 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 腐烂的橘子 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] input1 = { { 2, 1, 1 }, { 1, 1, 0 }, { 0, 1, 1 } }; + Assertions.assertEquals(4, s.orangesRotting(input1)); + + int[][] input2 = { { 2, 1, 1 }, { 0, 1, 1 }, { 1, 0, 1 } }; + Assertions.assertEquals(-1, s.orangesRotting(input2)); + + int[][] input3 = { { 0, 2 } }; + Assertions.assertEquals(0, s.orangesRotting(input3)); + + int[][] input4 = { { 1 } }; + Assertions.assertEquals(-1, s.orangesRotting(input4)); + + int[][] input5 = { { 1, 2 } }; + Assertions.assertEquals(1, s.orangesRotting(input5)); + } + + static class Solution { + + // 四个方向偏移量(上、下、左、右) + private static final int[][] directions = { { 0, 1 }, { 0, -1 }, { -1, 0 }, { 1, 0 } }; + + public int orangesRotting(int[][] grid) { + + int m = grid.length, n = grid[0].length; + + int freshCount = 0; + boolean[][] visited = new boolean[m][n]; + LinkedList queue = new LinkedList<>(); + + // 把所有腐烂的橘子加入队列,作为 BFS 的起点 + for (int x = 0; x < m; x++) { + for (int y = 0; y < n; y++) { + if (grid[x][y] == 1) { + freshCount++; + } else if (grid[x][y] == 2) { + queue.offer(new int[] { x, y }); + visited[x][y] = true; + } + } + } + if (freshCount == 0) { return 0; } + if (queue.isEmpty()) { return -1; } + + int step = 1; + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + int[] point = queue.poll(); + int x = point[0], y = point[1]; + for (int[] d : directions) { + int nextX = x + d[0], nextY = y + d[1]; + // 超出边界,跳过 + if (nextX < 0 || nextX >= m || nextY < 0 || nextY >= n) { continue; } + // 已访问,跳过(避免死循环) + if (visited[nextX][nextY]) { continue; } + // 遇到空格,跳过 + if (grid[nextX][nextY] == 0) { continue; } + // 遇到新鲜橘子,被传播腐烂 + if (grid[nextX][nextY] == 1) { + grid[nextX][nextY] = 2; + freshCount--; + // 新鲜橘子数为 0,返回结果 + if (freshCount == 0) { return step; } + } + visited[nextX][nextY] = true; + queue.offer(new int[] { nextX, nextY }); + } + } + step++; + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\264\246\346\210\267\345\220\210\345\271\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\264\246\346\210\267\345\220\210\345\271\266.java" new file mode 100644 index 0000000..3afee28 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\264\246\346\210\267\345\220\210\345\271\266.java" @@ -0,0 +1,112 @@ +package io.github.dunwu.algorithm.bfs; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 721. 账户合并 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 账户合并 { + + public static void main(String[] args) { + + Solution solution = new Solution(); + + String[][] input1 = { + { "John", "johnsmith@mail.com", "john00@mail.com" }, + { "John", "johnnybravo@mail.com" }, + { "John", "johnsmith@mail.com", "john_newyork@mail.com" }, + { "Mary", "mary@mail.com" } + }; + String[][] expect1 = { + { "John", "johnnybravo@mail.com" }, + { "John", "john00@mail.com", "john_newyork@mail.com", "johnsmith@mail.com" }, + { "Mary", "mary@mail.com" } + }; + List> output1 = solution.accountsMerge(ArrayUtil.toStringMatrixList(input1)); + Assertions.assertArrayEquals(expect1, ArrayUtil.toStringMatrixArray(output1)); + + String[][] input2 = { + { "Gabe", "Gabe0@m.co", "Gabe3@m.co", "Gabe1@m.co" }, + { "Kevin", "Kevin3@m.co", "Kevin5@m.co", "Kevin0@m.co" }, + { "Ethan", "Ethan5@m.co", "Ethan4@m.co", "Ethan0@m.co" }, + { "Hanzo", "Hanzo3@m.co", "Hanzo1@m.co", "Hanzo0@m.co" }, + { "Fern", "Fern5@m.co", "Fern1@m.co", "Fern0@m.co" } + }; + String[][] expect2 = { + { "Hanzo", "Hanzo0@m.co", "Hanzo1@m.co", "Hanzo3@m.co" }, + { "Fern", "Fern0@m.co", "Fern1@m.co", "Fern5@m.co" }, + { "Gabe", "Gabe0@m.co", "Gabe1@m.co", "Gabe3@m.co" }, + { "Kevin", "Kevin0@m.co", "Kevin3@m.co", "Kevin5@m.co" }, + { "Ethan", "Ethan0@m.co", "Ethan4@m.co", "Ethan5@m.co" } + }; + List> output2 = solution.accountsMerge(ArrayUtil.toStringMatrixList(input2)); + Assertions.assertArrayEquals(expect2, ArrayUtil.toStringMatrixArray(output2)); + } + + static class Solution { + + public List> accountsMerge(List> accounts) { + // key: email, value: 出现该 email 的 account 的索引列表 + HashMap> emailToIdx = new HashMap<>(); + for (int i = 0; i < accounts.size(); i++) { + List account = accounts.get(i); + for (int j = 1; j < account.size(); j++) { + String email = account.get(j); + List indexes = emailToIdx.getOrDefault(email, new ArrayList<>()); + indexes.add(i); + emailToIdx.put(email, indexes); + } + } + + // 计算合并后的账户 + List> res = new ArrayList<>(); + HashSet visitedEmails = new HashSet<>(); + + for (String email : emailToIdx.keySet()) { + if (visitedEmails.contains(email)) { + continue; + } + // 合并账户,用 BFS 算法穷举所有和 email 相关联的邮箱 + LinkedList mergedEmail = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(email); + visitedEmails.add(email); + // BFS 算法框架 + while (!queue.isEmpty()) { + String curEmail = queue.poll(); + mergedEmail.addLast(curEmail); + List indexes = emailToIdx.get(curEmail); + for (int index : indexes) { + List account = accounts.get(index); + for (int j = 1; j < account.size(); j++) { + String nextEmail = account.get(j); + if (!visitedEmails.contains(nextEmail)) { + queue.offer(nextEmail); + visitedEmails.add(nextEmail); + } + } + } + } + String userName = accounts.get(emailToIdx.get(email).get(0)).get(0); + // mergedEmail 是 userName 的所有邮箱 + Collections.sort(mergedEmail); + mergedEmail.addFirst(userName); + res.add(mergedEmail); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\277\267\345\256\253\344\270\255\347\246\273\345\205\245\345\217\243\346\234\200\350\277\221\347\232\204\345\207\272\345\217\243.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\277\267\345\256\253\344\270\255\347\246\273\345\205\245\345\217\243\346\234\200\350\277\221\347\232\204\345\207\272\345\217\243.java" new file mode 100644 index 0000000..4782ed7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\350\277\267\345\256\253\344\270\255\347\246\273\345\205\245\345\217\243\346\234\200\350\277\221\347\232\204\345\207\272\345\217\243.java" @@ -0,0 +1,79 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1926. 迷宫中离入口最近的出口 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 迷宫中离入口最近的出口 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + char[][] maze1 = { { '+', '+', '.', '+' }, { '.', '.', '.', '+' }, { '+', '+', '+', '.' } }; + int[] entrance1 = { 1, 2 }; + Assertions.assertEquals(1, s.nearestExit(maze1, entrance1)); + + char[][] maze2 = { { '+', '+', '+' }, { '.', '.', '.' }, { '+', '+', '+' } }; + int[] entrance2 = { 1, 0 }; + Assertions.assertEquals(2, s.nearestExit(maze2, entrance2)); + + char[][] maze3 = { { '.', '+' } }; + int[] entrance3 = { 0, 0 }; + Assertions.assertEquals(-1, s.nearestExit(maze3, entrance3)); + + char[][] maze4 = { + { '+', '.', '+', '+', '+', '+', '+' }, + { '+', '.', '+', '.', '.', '.', '+' }, + { '+', '.', '+', '.', '+', '.', '+' }, + { '+', '.', '.', '.', '+', '.', '+' }, + { '+', '+', '+', '+', '+', '+', '.' } + }; + int[] entrance4 = { 0, 1 }; + Assertions.assertEquals(-1, s.nearestExit(maze4, entrance4)); + } + + static class Solution { + + public int nearestExit(char[][] maze, int[] entrance) { + + int m = maze.length, n = maze[0].length; + final int[][] directions = { { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } }; + + // BFS 算法的队列和 visited 数组 + LinkedList queue = new LinkedList<>(); + boolean[][] visited = new boolean[m][n]; + queue.offer(entrance); + visited[entrance[0]][entrance[1]] = true; + // 启动 BFS 算法从 entrance 开始像四周扩散 + int step = 0; + while (!queue.isEmpty()) { + int size = queue.size(); + step++; + // 扩散当前队列中的所有节点 + for (int i = 0; i < size; i++) { + int[] point = queue.poll(); + // 每个节点都会尝试向上下左右四个方向扩展一步 + for (int[] d : directions) { + int x = point[0] + d[0], y = point[1] + d[1]; + if (x < 0 || x >= m || y < 0 || y >= n) { continue; } + if (visited[x][y] || maze[x][y] == '+') { continue; } + // 走到边界(出口) + if (x == 0 || x == m - 1 || y == 0 || y == n - 1) { return step; } + visited[x][y] = true; + queue.offer(new int[] { x, y }); + } + } + } + return -1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\351\222\245\345\214\231\345\222\214\346\210\277\351\227\264.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\351\222\245\345\214\231\345\222\214\346\210\277\351\227\264.java" new file mode 100644 index 0000000..3464b06 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/bfs/\351\222\245\345\214\231\345\222\214\346\210\277\351\227\264.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.bfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * 841. 钥匙和房间 + * + * @author Zhang Peng + * @date 2025-11-07 + */ +public class 钥匙和房间 { + + public static void main(String[] args) { + Solution s = new Solution(); + + List> input1 = new LinkedList<>(); + input1.add(Collections.singletonList(1)); + input1.add(Collections.singletonList(2)); + input1.add(Collections.singletonList(3)); + input1.add(new LinkedList<>()); + Assertions.assertTrue(s.canVisitAllRooms(input1)); + } + + static class Solution { + + public boolean canVisitAllRooms(List> rooms) { + // base case + if (rooms == null || rooms.size() == 0) { return true; } + + // 记录访问过的房间 + Set visited = new HashSet<>(); + LinkedList queue = new LinkedList<>(); + // 在队列中加入起点,启动 BFS + queue.offer(0); + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + Integer cur = queue.poll(); + if (!visited.contains(cur)) { + visited.add(cur); + for (int room : rooms.get(cur)) { + queue.offer(room); + } + } + } + } + return visited.size() == rooms.size(); + } + + } + +} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IHeap.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IHeap.java similarity index 83% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IHeap.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IHeap.java index c8b6203..259df53 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IHeap.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IHeap.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; /** * In computer science, a heap is a specialized tree-based data structure that satisfies the heap property: If A is a @@ -7,66 +7,74 @@ * the root node (this kind of heap is called max heap) or the keys of parent nodes are less than or equal to those of * the children (min heap). *

+ * * @author Justin Wetherell - * @see Heap (Wikipedia) - *
+ * @see Heap (Wikipedia)
*/ public interface IHeap { /** * Add value to the heap. + * * @param value to add to the heap. * @return True if added to the heap. */ - public boolean add(T value); + boolean add(T value); /** * Get the value of the head node from the heap. + * * @return value of the head node. */ - public T getHeadValue(); + T getHeadValue(); /** * Remove the head node from the heap. + * * @return value of the head node. */ - public T removeHead(); + T removeHead(); /** * Remove the value from the heap. + * * @param value to remove from heap. * @return True if value was removed form the heap; */ - public T remove(T value); + T remove(T value); /** * Clear the entire heap. */ - public void clear(); + void clear(); /** * Does the value exist in the heap. Warning this is a O(n) operation. + * * @param value to locate in the heap. * @return True if the value is in heap. */ - public boolean contains(T value); + boolean contains(T value); /** * Get size of the heap. + * * @return size of the heap. */ - public int size(); + int size(); /** * Validate the heap according to the invariants. + * * @return True if the heap is valid. */ - public boolean validate(); + boolean validate(); /** * Get this Heap as a Java compatible Collection + * * @return Java compatible Collection */ - public java.util.Collection toCollection(); + java.util.Collection toCollection(); } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IList.java similarity index 76% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IList.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IList.java index 613b630..6869238 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IList.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IList.java @@ -1,63 +1,70 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; /** * A list or sequence is an abstract data type that implements an ordered collection of values, where the same value may * occur more than once. *

+ * * @author Justin Wetherell - * @see List (Wikipedia) - *
+ * @see List (Wikipedia)
*/ public interface IList { /** * Add value to list. + * * @param value to add. * @return True if added. */ - public boolean add(T value); + boolean add(T value); /** * Remove value from list. + * * @param value to remove. * @return True if removed. */ - public boolean remove(T value); + boolean remove(T value); /** * Clear the entire list. */ - public void clear(); + void clear(); /** * Does the list contain value. + * * @param value to search list for. * @return True if list contains value. */ - public boolean contains(T value); + boolean contains(T value); /** * Size of the list. + * * @return size of the list. */ - public int size(); + int size(); /** * Validate the list according to the invariants. + * * @return True if the list is valid. */ - public boolean validate(); + boolean validate(); /** * Get this List as a Java compatible List + * * @return Java compatible List */ - public java.util.List toList(); + java.util.List toList(); /** * Get this List as a Java compatible Collection + * * @return Java compatible Collection */ - public java.util.Collection toCollection(); + java.util.Collection toCollection(); } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IMap.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IMap.java similarity index 78% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IMap.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IMap.java index 458ac28..3860756 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IMap.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IMap.java @@ -1,65 +1,73 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; /** * In computer science, an associative array, map, or dictionary is an abstract data type composed of a collection of * (key, value) pairs, such that each possible key appears at most once in the collection. *

+ * * @author Justin Wetherell - * @see Associative Array (Wikipedia) - *
+ * @see Associative Array + * (Wikipedia)
*/ public interface IMap { /** * Put key->value pair in the map. - * @param key to be inserted. + * + * @param key to be inserted. * @param value to be inserted. * @return V previous value or null if none. */ - public V put(K key, V value); + V put(K key, V value); /** * Get value for key. + * * @param key to get value for. * @return value mapped to key. */ - public V get(K key); + V get(K key); /** * Remove key and value from map. + * * @param key to remove from the map. * @return True if removed or False if not found. */ - public V remove(K key); + V remove(K key); /** * Clear the entire map. */ - public void clear(); + void clear(); /** * Does the map contain the key. + * * @param key to locate in the map. * @return True if key is in the map. */ - public boolean contains(K key); + boolean contains(K key); /** * Number of key/value pairs in the hash map. + * * @return number of key/value pairs. */ - public int size(); + int size(); /** * Validate the map according to the invariants. + * * @return True if the map is valid. */ - public boolean validate(); + boolean validate(); /** * Wraps this map in a Java compatible Map + * * @return Java compatible Map */ - public java.util.Map toMap(); + java.util.Map toMap(); } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IQueue.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IQueue.java similarity index 81% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IQueue.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IQueue.java index 8cef376..0a5c724 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IQueue.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IQueue.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; /** * A queue is a particular kind of abstract data type or collection in which the entities in the collection are kept in @@ -6,72 +6,82 @@ * position and removal of entities from the front terminal position. This makes the queue a First-In-First-Out (FIFO) * data structure. In a FIFO data structure, the first element added to the queue will be the first one to be removed. *

+ * * @author Justin Wetherell - * @see Queue (Wikipedia) - *
+ * @see Queue + * (Wikipedia)
*/ public interface IQueue { /** * Add a value to the beginning of the queue. + * * @param value to add to queue. * @return True if added to queue. */ - public boolean offer(T value); + boolean offer(T value); /** * Remove a value from the tail of the queue. + * * @return value from the tail of the queue. */ - public T poll(); + T poll(); /** * Get but do not remove tail of the queue. + * * @return value from the tail of the queue. */ - public T peek(); + T peek(); /** * Remove the value from the queue. + * * @param value to remove from the queue. * @return True if the value was removed from the queue. */ - public boolean remove(T value); + boolean remove(T value); /** * Clear the entire queue. */ - public void clear(); + void clear(); /** * Does the queue contain the value. + * * @param value to find in the queue. * @return True if the queue contains the value. */ - public boolean contains(T value); + boolean contains(T value); /** * Get the size of the queue. + * * @return size of the queue. */ - public int size(); + int size(); /** * Validate the queue according to the invariants. + * * @return True if the queue is valid. */ - public boolean validate(); + boolean validate(); /** * Get this Queue as a Java compatible Queue + * * @return Java compatible Queue */ - public java.util.Queue toQueue(); + java.util.Queue toQueue(); /** * Get this Queue as a Java compatible Collection + * * @return Java compatible Collection */ - public java.util.Collection toCollection(); + java.util.Collection toCollection(); } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISet.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISet.java similarity index 79% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISet.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISet.java index 75fbe59..550d0a8 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISet.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISet.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; /** * In computer science, a set is an abstract data structure that can store certain values, without any particular order, @@ -6,60 +6,68 @@ * other collection types, rather than retrieving a specific element from a set, one typically tests a value for * membership in a set. *

+ * * @author Justin Wetherell - * @see Set (Wikipedia) - *
+ * @see Set + * (Wikipedia)
*/ public interface ISet { /** * Add value to set. + * * @param value to add. * @return True if added. */ - public boolean add(T value); + boolean add(T value); /** * Remove value from set. + * * @param value to remove. * @return True if removed. */ - public boolean remove(T value); + boolean remove(T value); /** * Clear the entire set. */ - public void clear(); + void clear(); /** * Does the set contain value. + * * @param value to search set for. * @return True if set contains value. */ - public boolean contains(T value); + boolean contains(T value); /** * Size of the set. + * * @return size of the set. */ - public int size(); + int size(); /** * Validate the set according to the invariants. + * * @return True if the set is valid. */ - public boolean validate(); + boolean validate(); /** * Get this Set as a Java compatible Set + * * @return Java compatible Set */ - public java.util.Set toSet(); + java.util.Set toSet(); /** * Get this Set as a Java compatible Collection + * * @return Java compatible Collection */ - public java.util.Collection toCollection(); + java.util.Collection toCollection(); } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IStack.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IStack.java similarity index 81% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/IStack.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IStack.java index 9a517f3..5904d27 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/IStack.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/IStack.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; /** * A stack is a last in, first out (LIFO) abstract data type and linear data structure. A stack can have any abstract @@ -7,71 +7,81 @@ * contain enough space to accept the given item, the stack is then considered to be in an overflow state. The pop * operation removes an item from the top of the stack. *

+ * * @author Justin Wetherell - * @see Stack (Wikipedia) - *
+ * @see Stack + * (Wikipedia)
*/ public interface IStack { /** * Push value on top of stack + * * @param value to push on the stack. */ - public boolean push(T value); + boolean push(T value); /** * Pop the value from the top of stack. + * * @return value popped off the top of the stack. */ - public T pop(); + T pop(); /** * Peek the value from the top of stack. + * * @return value popped off the top of the stack. */ - public T peek(); + T peek(); /** * Remove value from stack. + * * @param value to remove from stack. * @return True if value was removed. */ - public boolean remove(T value); + boolean remove(T value); /** * Clear the entire stack. */ - public void clear(); + void clear(); /** * Does stack contain object. + * * @param value object to find in stack. * @return True is stack contains object. */ - public boolean contains(T value); + boolean contains(T value); /** * Size of the stack. + * * @return size of the stack. */ - public int size(); + int size(); /** * Validate the stack according to the invariants. + * * @return True if the stack is valid. */ - public boolean validate(); + boolean validate(); /** * Get this Stack as a Java compatible Queue + * * @return Java compatible Queue */ - public java.util.Queue toLifoQueue(); + java.util.Queue toLifoQueue(); /** * Get this Stack as a Java compatible Collection + * * @return Java compatible Collection */ - public java.util.Collection toCollection(); + java.util.Collection toCollection(); } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISuffixTree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISuffixTree.java similarity index 86% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISuffixTree.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISuffixTree.java index 151e500..af24df2 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ISuffixTree.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ISuffixTree.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; import java.util.Set; @@ -7,6 +7,7 @@ * containing all the suffixes of the given text as their keys and positions in the text as their values. Suffix trees * allow particularly fast implementations of many important string operations. *

+ * * @author Justin Wetherell * @see Suffix Tree (Wikipedia) *
@@ -15,14 +16,17 @@ public interface ISuffixTree { /** * Does the sub-sequence exist in the suffix tree. + * * @param sub-sequence to locate in the tree. * @return True if the sub-sequence exist in the tree. */ - public boolean doesSubStringExist(C sub); + boolean doesSubStringExist(C sub); /** * Get all the suffixes in the tree. + * * @return set of suffixes in the tree. */ - public Set getSuffixes(); + Set getSuffixes(); + } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ITree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ITree.java similarity index 84% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/common/ITree.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ITree.java index 14c415e..6036192 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/common/ITree.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/common/ITree.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; /** * A tree can be defined recursively (locally) as a collection of nodes (starting at a root node), where each node is a @@ -6,6 +6,7 @@ * node is duplicated. A tree can be defined abstractly as a whole (globally) as an ordered tree, with a value assigned * to each node. *

+ * * @author Justin Wetherell * @see Tree (Wikipedia) *
@@ -14,46 +15,52 @@ public interface ITree { /** * Add value to the tree. Tree can contain multiple equal values. + * * @param value to add to the tree. * @return True if successfully added to tree. */ - public boolean add(T value); + boolean add(T value); /** * Remove first occurrence of value in the tree. + * * @param value to remove from the tree. * @return T value removed from tree. */ - public T remove(T value); + T remove(T value); /** * Clear the entire stack. */ - public void clear(); + void clear(); /** * Does the tree contain the value. + * * @param value to locate in the tree. * @return True if tree contains value. */ - public boolean contains(T value); + boolean contains(T value); /** * Get number of nodes in the tree. + * * @return Number of nodes in the tree. */ - public int size(); + int size(); /** * Validate the tree according to the invariants. + * * @return True if the tree is valid. */ - public boolean validate(); + boolean validate(); /** * Get Tree as a Java compatible Collection + * * @return Java compatible Collection */ - public java.util.Collection toCollection(); + java.util.Collection toCollection(); } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/LRU\347\274\223\345\255\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/LRU\347\274\223\345\255\230.java" new file mode 100644 index 0000000..7d4ebd1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/LRU\347\274\223\345\255\230.java" @@ -0,0 +1,63 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.Iterator; +import java.util.LinkedHashMap; + +/** + * 146. LRU 缓存 + * + * @author Zhang Peng + * @date 2025-10-31 + */ +public class LRU缓存 { + + public static void main(String[] args) { + + LRUCache lRUCache = new LRUCache(2); + lRUCache.put(1, 1); // 缓存是 {1=1} + lRUCache.put(2, 2); // 缓存是 {1=1, 2=2} + Assertions.assertEquals(1, lRUCache.get(1)); + lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3} + Assertions.assertEquals(-1, lRUCache.get(2)); + lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3} + Assertions.assertEquals(-1, lRUCache.get(1)); + Assertions.assertEquals(3, lRUCache.get(3)); + Assertions.assertEquals(4, lRUCache.get(4)); + } + + static class LRUCache { + + private int capacity = 0; + private LinkedHashMap cache = null; + + public LRUCache(int capacity) { + this.capacity = capacity; + this.cache = new LinkedHashMap<>(capacity); + } + + public int get(int key) { + Integer val = cache.get(key); + if (val != null) { + cache.remove(key); + cache.put(key, val); + } + return val == null ? -1 : val; + } + + public void put(int key, int value) { + if (cache.containsKey(key)) { + cache.remove(key); + } else { + if (capacity <= cache.size()) { + Iterator iterator = cache.keySet().iterator(); + cache.remove(iterator.next()); + } + } + cache.put(key, value); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\250.java" new file mode 100644 index 0000000..67de9b1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\250.java" @@ -0,0 +1,89 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * 224. 基本计算器 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 基本计算器 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(23, s.calculate("(1+(4+5+2)-3)+(6+8)")); + Assertions.assertEquals(12, s.calculate("1+(4+5+2)")); + Assertions.assertEquals(2147483647, s.calculate("2147483647")); + Assertions.assertEquals(2, s.calculate("1 + 1")); + Assertions.assertEquals(3, s.calculate("2 - 1 + 2")); + } + + static class Solution { + + public int calculate(String s) { + Stack stack = new Stack<>(); + Map map = new HashMap<>(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '(') { + stack.push(i); + } else if (s.charAt(i) == ')') { + map.put(stack.pop(), i); + } + } + return calculate(s, 0, s.length() - 1, map); + } + + public int calculate(String s, int start, int end, Map map) { + int num = 0; + char sign = '+'; + Stack stack = new Stack<>(); + for (int i = start; i <= end; i++) { + char c = s.charAt(i); + if (Character.isDigit(c)) { + num = num * 10 + (c - '0'); + } + if (c == '(') { + num = calculate(s, i + 1, map.get(i) - 1, map); + i = map.get(i); + } + + if (c == '+' || c == '-' || c == '*' || c == '/' || i == end) { + int pre = 0; + switch (sign) { + case '+': + stack.push(num); + break; + case '-': + stack.push(-num); + break; + case '*': + pre = stack.pop(); + stack.push(pre * num); + break; + case '/': + pre = stack.pop(); + stack.push(pre / num); + break; + default: + break; + } + sign = c; + num = 0; + } + } + + int result = 0; + while (!stack.isEmpty()) { + result += stack.pop(); + } + return result; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\2502.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\2502.java" new file mode 100644 index 0000000..6fe15e1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\345\237\272\346\234\254\350\256\241\347\256\227\345\231\2502.java" @@ -0,0 +1,72 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 227. 基本计算器 II + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 基本计算器2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2147483647, s.calculate("2147483647")); + Assertions.assertEquals(2, s.calculate("1 + 1")); + Assertions.assertEquals(3, s.calculate("2 - 1 + 2")); + Assertions.assertEquals(7, s.calculate("3+2*2")); + } + + static class Solution { + + public int calculate(String s) { + return calculate(s, 0, s.length() - 1); + } + + public int calculate(String s, int start, int end) { + int num = 0; + char sign = '+'; + Stack stack = new Stack<>(); + for (int i = start; i <= end; i++) { + char c = s.charAt(i); + if (Character.isDigit(c)) { + num = num * 10 + (c - '0'); + } + if (c == '+' || c == '-' || c == '*' || c == '/' || i == s.length() - 1) { + int pre = 0; + switch (sign) { + case '+': + stack.push(num); + break; + case '-': + stack.push(-num); + break; + case '*': + pre = stack.pop(); + stack.push(pre * num); + break; + case '/': + pre = stack.pop(); + stack.push(pre / num); + break; + default: + break; + } + sign = c; + num = 0; + } + } + + int result = 0; + while (!stack.isEmpty()) { + result += stack.pop(); + } + return result; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250.java" new file mode 100644 index 0000000..58424c4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\210\221\347\232\204\346\227\245\347\250\213\345\256\211\346\216\222\350\241\250.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.TreeMap; + +/** + * 729. 我的日程安排表 I + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 我的日程安排表 { + + public static void main(String[] args) { + MyCalendar s = new MyCalendar(); + Assertions.assertTrue(s.book(10, 20)); + Assertions.assertFalse(s.book(15, 25)); + Assertions.assertTrue(s.book(20, 30)); + } + + static class MyCalendar { + + private TreeMap calendar = null; + + public MyCalendar() { + calendar = new TreeMap<>(); + } + + public boolean book(int start, int end) { + Integer earlier = calendar.floorKey(start); + Integer later = calendar.ceilingKey(start); + if (later != null && later < end) { + return false; + } + if (earlier != null && start < calendar.get(earlier)) { + return false; + } + calendar.put(start, end); + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214.java" new file mode 100644 index 0000000..e1f68e8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214.java" @@ -0,0 +1,43 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; + +/** + * 950. 按递增顺序显示卡牌 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 按递增顺序显示卡牌 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 2, 13, 3, 11, 5, 17, 7 } + , s.deckRevealedIncreasing(new int[] { 17, 13, 11, 2, 3, 5, 7 })); + } + + static class Solution { + + public int[] deckRevealedIncreasing(int[] deck) { + int n = deck.length; + LinkedList res = new LinkedList<>(); + Arrays.sort(deck); + for (int i = n - 1; i >= 0; i--) { + if (!res.isEmpty()) { + res.addFirst(res.removeLast()); + } + res.addFirst(deck[i]); + } + int[] arr = new int[n]; + for (int i = 0; i < res.size(); i++) { + arr[i] = res.get(i); + } + return arr; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\227\240\346\263\225\345\220\203\345\215\210\351\244\220\347\232\204\345\255\246\347\224\237\346\225\260\351\207\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\227\240\346\263\225\345\220\203\345\215\210\351\244\220\347\232\204\345\255\246\347\224\237\346\225\260\351\207\217.java" new file mode 100644 index 0000000..2f0b46d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\346\227\240\346\263\225\345\220\203\345\215\210\351\244\220\347\232\204\345\255\246\347\224\237\346\225\260\351\207\217.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.design; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1700. 无法吃午餐的学生数量 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 无法吃午餐的学生数量 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(0, s.countStudents(new int[] { 1, 1, 0, 0 }, new int[] { 0, 1, 0, 1 })); + Assertions.assertEquals(3, s.countStudents(new int[] { 1, 1, 1, 0, 0, 1 }, new int[] { 1, 0, 0, 0, 1, 1 })); + } + + static class Solution { + + public int countStudents(int[] students, int[] sandwiches) { + int total = students.length; + LinkedList studentQueue = new LinkedList<>(); + for (int s : students) { + studentQueue.addLast(s); + } + int matchNum = 0; + while (matchNum < sandwiches.length) { + int notMatchNum = 0; + int size = studentQueue.size(); + while (notMatchNum < size) { + Integer s = studentQueue.removeFirst(); + if (s == sandwiches[matchNum]) { + matchNum++; + break; + } else { + studentQueue.addLast(s); + notMatchNum++; + } + } + if (notMatchNum == size) { + break; + } + } + return total - matchNum; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\350\256\241\347\256\227\345\231\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\350\256\241\347\256\227\345\231\250.java" new file mode 100644 index 0000000..5e32109 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/design/\350\256\241\347\256\227\345\231\250.java" @@ -0,0 +1,31 @@ +package io.github.dunwu.algorithm.design; + +import java.util.Arrays; + +/** + * 计数器模板 + * + * @author Zhang Peng + * @date 2025-10-31 + */ +public class 计算器 { + + public static void main(String[] args) { + Solution s = new Solution(); + System.out.println("args = " + Arrays.toString(args)); + } + + static class Solution { + + public int toNum(String s) { + if (s == null || s.length() == 0) { return 0; } + int num = 0; + for (char c : s.toCharArray()) { + num = num * 10 + (c - '0'); + } + return num; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/package-info.java new file mode 100644 index 0000000..6b44e2d --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/package-info.java @@ -0,0 +1,7 @@ +/** + * DFS 解岛屿数类型问题 + * + * @author Zhang Peng + * @date 2025-12-15 + */ +package io.github.dunwu.algorithm.dfs.island; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\346\225\260\351\207\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\346\225\260\351\207\217.java" new file mode 100644 index 0000000..c6bafac --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\346\225\260\351\207\217.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 200. 岛屿数量 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 岛屿数量 { + + public static void main(String[] args) { + Solution s = new Solution(); + + char[][] input = { + { '1', '1', '1', '1', '0' }, + { '1', '1', '0', '1', '0' }, + { '1', '1', '0', '0', '0' }, + { '0', '0', '0', '0', '0' } + }; + Assertions.assertEquals(1, s.numIslands(input)); + + char[][] input2 = { + { '1', '1', '0', '0', '0' }, + { '1', '1', '0', '0', '0' }, + { '0', '0', '1', '0', '0' }, + { '0', '0', '0', '1', '1' } + }; + Assertions.assertEquals(3, s.numIslands(input2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int numIslands(char[][] grid) { + if (grid == null || grid.length == 0) { return 0; } + int res = 0; + int m = grid.length, n = grid[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == '1') { + // 每发现一个岛屿,岛屿数量加一 + res++; + // 然后使用 DFS 将岛屿淹了 + dfs(grid, i, j); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public void dfs(char[][] grid, int x, int y) { + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return; } + if (grid[x][y] == '0') { return; } + + grid[x][y] = '0'; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + dfs(grid, i, j); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.java" new file mode 100644 index 0000000..df3719e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\345\262\233\345\261\277\347\232\204\346\234\200\345\244\247\351\235\242\347\247\257.java" @@ -0,0 +1,71 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 695. 岛屿的最大面积 + * + * @author Zhang Peng + * @date 2025-11-05 + */ +public class 岛屿的最大面积 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] input = { + { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0 }, + { 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 } + }; + Assertions.assertEquals(6, s.maxAreaOfIsland(input)); + + int[][] input2 = { { 0, 0, 0, 0, 0, 0, 0, 0 } }; + Assertions.assertEquals(0, s.maxAreaOfIsland(input2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int maxAreaOfIsland(int[][] grid) { + + // base case + if (grid == null || grid.length == 0) return 0; + + int res = 0; + int m = grid.length, n = grid[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == 1) { + int size = dfs(grid, i, j); + res = Math.max(res, size); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public int dfs(int[][] grid, int x, int y) { + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return 0; } + if (grid[x][y] == 0) { return 0; } + + int cnt = 1; + grid[x][y] = 0; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + cnt += dfs(grid, i, j); + } + return cnt; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\255\220\345\262\233\345\261\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\255\220\345\262\233\345\261\277.java" new file mode 100644 index 0000000..535ae08 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\255\220\345\262\233\345\261\277.java" @@ -0,0 +1,93 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 1905. 统计子岛屿 + * + * @author Zhang Peng + * @date 2025-11-05 + */ +public class 统计子岛屿 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] gridA1 = { + { 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0 }, + { 1, 1, 0, 1, 1 } + }; + int[][] gridB1 = { + { 1, 1, 1, 0, 0 }, + { 0, 0, 1, 1, 1 }, + { 0, 1, 0, 0, 0 }, + { 1, 0, 1, 1, 0 }, + { 0, 1, 0, 1, 0 } + }; + Assertions.assertEquals(3, s.countSubIslands(gridA1, gridB1)); + + int[][] gridA2 = { + { 1, 0, 1, 0, 1 }, + { 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 1 }, + { 1, 0, 1, 0, 1 } + }; + int[][] gridB2 = { + { 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 1 }, + { 0, 1, 0, 1, 0 }, + { 0, 1, 0, 1, 0 }, + { 1, 0, 0, 0, 1 } + }; + Assertions.assertEquals(2, s.countSubIslands(gridA2, gridB2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int countSubIslands(int[][] grid1, int[][] grid2) { + int m = grid1.length, n = grid1[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid1[i][j] == 0 && grid2[i][j] == 1) { + // 这个岛屿肯定不是子岛,淹掉 + dfs(grid2, i, j); + } + } + } + + int res = 0; + // 现在 grid2 中剩下的岛屿都是子岛,计算岛屿数量 + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid2[i][j] == 1) { + res++; + dfs(grid2, i, j); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public void dfs(int[][] grid, int x, int y) { + // base case + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return; } + if (grid[x][y] == 0) { return; } + + grid[x][y] = 0; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + dfs(grid, i, j); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.java" new file mode 100644 index 0000000..20975ca --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\347\273\237\350\256\241\345\260\201\351\227\255\345\262\233\345\261\277\347\232\204\346\225\260\347\233\256.java" @@ -0,0 +1,86 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 1254. 统计封闭岛屿的数目 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 统计封闭岛屿的数目 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] input = { + { 1, 1, 1, 1, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0, 1, 1, 0 }, + { 1, 0, 1, 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0, 1, 0, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 0 } + }; + Assertions.assertEquals(2, s.closedIsland(input)); + + int[][] input2 = { + { 1, 1, 1, 1, 1, 1, 1 }, + { 1, 0, 0, 0, 0, 0, 1 }, + { 1, 0, 1, 1, 1, 0, 1 }, + { 1, 0, 1, 0, 1, 0, 1 }, + { 1, 0, 1, 1, 1, 0, 1 }, + { 1, 0, 0, 0, 0, 0, 1 }, + { 1, 1, 1, 1, 1, 1, 1 } + }; + Assertions.assertEquals(2, s.closedIsland(input2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int closedIsland(int[][] grid) { + + // base case + if (grid == null || grid.length == 0) { return 0; } + + // 将靠边的岛屿淹没 + int m = grid.length, n = grid[0].length; + for (int j = 0; j < n; j++) { + dfs(grid, 0, j); + dfs(grid, m - 1, j); + } + for (int i = 0; i < m; i++) { + dfs(grid, i, 0); + dfs(grid, i, n - 1); + } + + int res = 0; + // 遍历 grid,剩下的岛屿都是封闭岛屿 + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == 0) { + res++; + dfs(grid, i, j); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public void dfs(int[][] grid, int x, int y) { + // base case + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return; } + if (grid[x][y] == 1) { return; } + + grid[x][y] = 1; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + dfs(grid, i, j); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.java" new file mode 100644 index 0000000..7bdaa0c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/island/\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.java" @@ -0,0 +1,80 @@ +package io.github.dunwu.algorithm.dfs.island; + +import org.junit.jupiter.api.Assertions; + +/** + * 1020. 飞地的数量/a> + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 飞地的数量 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] input = { + { 0, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 0, 1, 1, 0 }, + { 0, 0, 0, 0 } + }; + Assertions.assertEquals(3, s.numEnclaves(input)); + + int[][] input2 = { + { 0, 1, 1, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 0 } + }; + Assertions.assertEquals(0, s.numEnclaves(input2)); + } + + static class Solution { + + private final int[][] directions = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; + + public int numEnclaves(int[][] grid) { + + // base case + if (grid == null || grid.length == 0) { return 0; } + + int m = grid.length, n = grid[0].length; + for (int j = 0; j < n; j++) { + dfs(grid, 0, j); + dfs(grid, m - 1, j); + } + for (int i = 0; i < m; i++) { + dfs(grid, i, 0); + dfs(grid, i, n - 1); + } + + int res = 0; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] == 1) { + res += dfs(grid, i, j); + } + } + } + return res; + } + + // 淹没与 (x, y) 相邻的陆地,并返回淹没的陆地面积 + public int dfs(int[][] grid, int x, int y) { + int m = grid.length, n = grid[0].length; + if (x < 0 || x >= m || y < 0 || y >= n) { return 0; } + if (grid[x][y] == 0) { return 0; } + + int cnt = 1; + grid[x][y] = 0; + for (int[] d : directions) { + int i = x + d[0], j = y + d[1]; + cnt += dfs(grid, i, j); + } + return cnt; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/package-info.java new file mode 100644 index 0000000..3192edb --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/package-info.java @@ -0,0 +1,7 @@ +/** + * 通过回溯算法解决排列、组合、子集类型的问题 + * + * @author Zhang Peng + * @date 2025-12-13 + */ +package io.github.dunwu.algorithm.dfs.permutation_combination; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\227.java" new file mode 100644 index 0000000..6062839 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\227.java" @@ -0,0 +1,77 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 46. 全排列 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 全排列 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] expect = { { 1, 2, 3 }, { 1, 3, 2 }, { 2, 1, 3 }, { 2, 3, 1 }, { 3, 1, 2 }, { 3, 2, 1 } }; + int[][] expect2 = { { 0, 1 }, { 1, 0 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.permute(new int[] { 1, 2, 3 }))); + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(s.permute(new int[] { 0, 1 }))); + Assertions.assertArrayEquals(new int[][] { { 1 } }, ArrayUtil.toIntMatrixArray(s.permute(new int[] { 1 }))); + } + + static class Solution { + + // 「路径」中的元素会被标记为 true,避免重复使用 + boolean[] visited; + // 记录「路径」 + LinkedList path; + List> res; + + // 主函数,输入一组不重复的数字,返回它们的全排列 + List> permute(int[] nums) { + visited = new boolean[nums.length]; + path = new LinkedList<>(); + res = new LinkedList<>(); + backtrack(nums); + return res; + } + + // 路径:记录在 path 中 + // 选择列表:nums 中不存在于 path 的那些元素(visited[i] 为 false) + // 结束条件:nums 中的元素全都在 path 中出现 + void backtrack(int[] nums) { + // 【结束】【前序】到达决策树叶子节点,可以记录结果 + if (path.size() == nums.length) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + return; + } + + for (int i = 0; i < nums.length; i++) { + + // 排除不合法的选择 + // nums[i] 已经在 path 中,跳过 + if (visited[i]) { continue; } + + // 【选择】 + path.add(nums[i]); + visited[i] = true; + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums); + + // 【取消选择】 + path.removeLast(); + visited[i] = false; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\2272.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\2272.java" new file mode 100644 index 0000000..038cdda --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\205\250\346\216\222\345\210\2272.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 47. 全排列 II + * LCR 084. 全排列 II + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 全排列2 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] expect = { { 1, 1, 2 }, { 1, 2, 1 }, { 2, 1, 1 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.permuteUnique(new int[] { 1, 1, 2 }))); + + int[][] expect2 = { { 1, 2, 3 }, { 1, 3, 2 }, { 2, 1, 3 }, { 2, 3, 1 }, { 3, 1, 2 }, { 3, 2, 1 } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(s.permuteUnique(new int[] { 1, 2, 3 }))); + } + + static class Solution { + + private boolean[] visited; + private List path; + private List> res; + + public List> permuteUnique(int[] nums) { + visited = new boolean[nums.length]; + path = new LinkedList<>(); + res = new LinkedList<>(); + Arrays.sort(nums); + backtrack(nums); + return res; + } + + public void backtrack(int[] nums) { + + // 【结束】【前序】到达决策树叶子节点,可以记录结果 + if (path.size() == nums.length) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + } + + for (int i = 0; i < nums.length; i++) { + + // 排除不合法的选择 + if (visited[i]) { continue; } + // 剪枝逻辑,固定相同的元素在排列中的相对位置 + if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) { continue; } + + // 【选择】 + visited[i] = true; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums); + + // 【取消选择】 + path.remove(path.size() - 1); + visited[i] = false; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\206.java" new file mode 100644 index 0000000..ce2377e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\206.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 78. 子集 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 子集 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] expect = { {}, { 1 }, { 1, 2 }, { 1, 2, 3 }, { 1, 3 }, { 2 }, { 2, 3 }, { 3 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.subsets(new int[] { 1, 2, 3 }))); + Assertions.assertArrayEquals(new int[][] { {}, { 0 } }, ArrayUtil.toIntMatrixArray(s.subsets(new int[] { 0 }))); + } + + static class Solution { + + private boolean[] visited; + private List path; + private List> res; + + // 主函数 + public List> subsets(int[] nums) { + visited = new boolean[nums.length]; + path = new LinkedList<>(); + res = new LinkedList<>(); + Arrays.sort(nums); + backtrack(nums, 0); + return res; + } + + // 回溯算法核心函数,遍历子集问题的回溯树 + public void backtrack(int[] nums, int start) { + + // 【结束】【前序】到达决策树叶子节点,可以记录结果 + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + + for (int i = start; i < nums.length; i++) { + + // 排除不合法的选择 + if (visited[i]) { continue; } + + // 【选择】 + visited[i] = true; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + visited[i] = false; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\2062.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\2062.java" new file mode 100644 index 0000000..d09fb5e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\345\255\220\351\233\2062.java" @@ -0,0 +1,71 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 90. 子集 II + *

+ * 元素可重复,不可复选 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 子集2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + int[][] expect = { {}, { 1 }, { 1, 2 }, { 1, 2, 2 }, { 2 }, { 2, 2 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.subsetsWithDup(new int[] { 1, 2, 2 }))); + + int[][] expect2 = { {}, { 0 } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(s.subsetsWithDup(new int[] { 0 }))); + } + + static class Solution { + + private List> res; + private LinkedList path; + + public List> subsetsWithDup(int[] nums) { + path = new LinkedList<>(); + res = new LinkedList<>(); + // 先排序,让相同的元素靠在一起 + Arrays.sort(nums); + backtrack(nums, 0); + return res; + } + + public void backtrack(int[] nums, int start) { + + // 【结束】 + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + + for (int i = start; i < nums.length; i++) { + + // 剪枝逻辑,值相同的相邻树枝,只遍历第一条 + if (i > start && nums[i] == nums[i - 1]) continue; + + // 【选择】 + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210.java" new file mode 100644 index 0000000..c29d190 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210.java" @@ -0,0 +1,61 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 77. 组合 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 组合 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] expect = { { 1, 2 }, { 1, 3 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(s.combine(4, 2))); + Assertions.assertArrayEquals(new int[][] { { 1 } }, ArrayUtil.toIntMatrixArray(s.combine(1, 1))); + } + + static class Solution { + + private List path; + private List> res; + + public List> combine(int n, int k) { + path = new LinkedList<>(); + res = new LinkedList<>(); + backtrack(n, k, 1); + return res; + } + + public void backtrack(int n, int k, int s) { + + // 【结束】 + if (path.size() == k) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + } + + for (int i = s; i <= n; i++) { + // 【选择】 + path.add(i); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + // 通过 start 参数控制树枝的遍历,避免产生重复的子集 + backtrack(n, k, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\214.java" new file mode 100644 index 0000000..8deb466 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\214.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 39. 组合总和 + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 组合总和 { + + public static void main(String[] args) { + Solution s = new Solution(); + + List> output = s.combinationSum(new int[] { 2, 3, 6, 7 }, 7); + Assertions.assertArrayEquals(new int[][] { { 2, 2, 3 }, { 7 } }, ArrayUtil.toIntMatrixArray(output)); + + List> output2 = s.combinationSum(new int[] { 2, 3, 5 }, 8); + Assertions.assertArrayEquals(new int[][] { { 2, 2, 2, 2 }, { 2, 3, 3 }, { 3, 5 } }, + ArrayUtil.toIntMatrixArray(output2)); + } + + static class Solution { + + private int sum; + private List path; + private List> res; + + public List> combinationSum(int[] candidates, int target) { + sum = 0; + path = new LinkedList<>(); + res = new LinkedList<>(); + Arrays.sort(candidates); + backtrack(candidates, target, 0); + return res; + } + + public void backtrack(int[] nums, int target, int start) { + + // 【结束】【前序】找到目标和,记录结果 + if (sum == target) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + return; + } + // base case,超过目标和,停止向下遍历 + if (sum > target) { return; } + + for (int i = start; i < nums.length; i++) { + // 【选择】 + sum += nums[i]; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + // 同一元素可重复使用,注意参数 + backtrack(nums, target, i); + + // 【取消选择】 + path.remove(path.size() - 1); + sum -= nums[i]; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2142.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2142.java" new file mode 100644 index 0000000..e57c58c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2142.java" @@ -0,0 +1,86 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 40. 组合总和 II + * LCR 082. 组合总和 II + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 组合总和2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + List> output = s.combinationSum2(new int[] { 10, 1, 2, 7, 6, 1, 5 }, 8); + int[][] expect = { { 1, 1, 6 }, { 1, 2, 5 }, { 1, 7 }, { 2, 6 } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(output)); + + List> output2 = s.combinationSum2(new int[] { 2, 5, 2, 1, 2 }, 5); + int[][] expect2 = { { 1, 2, 2 }, { 5 } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(output2)); + } + + static class Solution { + + private int sum; + private boolean[] visited; + private List path; + private List> res; + + public List> combinationSum2(int[] candidates, int target) { + sum = 0; + visited = new boolean[candidates.length]; + path = new ArrayList<>(); + res = new ArrayList<>(); + Arrays.sort(candidates); + backtrack(candidates, target, 0); + return res; + } + + public void backtrack(int[] nums, int target, int start) { + + // 【结束】【前序】找到目标和,记录结果 + if (sum == target) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + return; + } + // base case,超过目标和,停止向下遍历 + if (sum > target) { return; } + + for (int i = start; i < nums.length; i++) { + + // 剪枝逻辑 + if (visited[i]) { continue; } + if (i > start && nums[i] == nums[i - 1] && !visited[i - 1]) { continue; } + + // 【选择】 + sum += nums[i]; + visited[i] = true; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + backtrack(nums, target, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + visited[i] = false; + sum -= nums[i]; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2143.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2143.java" new file mode 100644 index 0000000..c0128a6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/permutation_combination/\347\273\204\345\220\210\346\200\273\345\222\2143.java" @@ -0,0 +1,83 @@ +package io.github.dunwu.algorithm.dfs.permutation_combination; + +import cn.hutool.core.collection.CollectionUtil; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 216. 组合总和 III + * + * @author Zhang Peng + * @date 2025-11-04 + */ +public class 组合总和3 { + + public static void main(String[] args) { + Solution s = new Solution(); + + Assertions.assertArrayEquals(new int[][] { { 1, 2, 4 } }, ArrayUtil.toIntMatrixArray(s.combinationSum3(3, 7))); + + int[][] expect2 = { { 1, 2, 6 }, { 1, 3, 5 }, { 2, 3, 4 } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toIntMatrixArray(s.combinationSum3(3, 9))); + + Assertions.assertArrayEquals(new int[][] {}, ArrayUtil.toIntMatrixArray(s.combinationSum3(4, 1))); + } + + static class Solution { + + private int sum; + private boolean[] visited; + private List path; + private List> res; + + public List> combinationSum3(int k, int n) { + sum = 0; + visited = new boolean[9]; + path = new LinkedList<>(); + res = new LinkedList<>(); + int[] nums = new int[9]; + for (int i = 0; i < 9; i++) { + nums[i] = i + 1; + } + backtrack(nums, n, k, 0); + return res; + } + + public void backtrack(int[] nums, int target, int k, int s) { + + // 【结束】【前序】找到目标和,记录结果 + if (sum == target && path.size() == k) { + res.add(new LinkedList<>(path)); + System.out.printf("【结果】 %s\n\n", CollectionUtil.join(path, " -> ")); + return; + } + // base case,超过目标和,停止向下遍历 + if (sum > target || path.size() > k) { return; } + + for (int i = s; i < nums.length; i++) { + + if (visited[i]) { continue; } + + // 【选择】 + sum += nums[i]; + visited[i] = true; + path.add(nums[i]); + System.out.printf("\t\t%s\n", CollectionUtil.join(path, " -> ")); + + // 【回溯】 + // 同一元素可重复使用,注意参数 + backtrack(nums, target, k, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + visited[i] = false; + sum -= nums[i]; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\216.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\216.java" new file mode 100644 index 0000000..9e15542 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\216.java" @@ -0,0 +1,96 @@ +package io.github.dunwu.algorithm.dfs.sudoku; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 51. N 皇后 + * + * @author Zhang Peng + * @since 2020-07-04 + */ +public class N皇后 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> output = s.solveNQueens(4); + String[][] expect = { { ".Q..", "...Q", "Q...", "..Q." }, { "..Q.", "Q...", "...Q", ".Q.." } }; + Assertions.assertArrayEquals(expect, ArrayUtil.toStringMatrixArray(output)); + + List> output2 = s.solveNQueens(1); + String[][] expect2 = { { "Q" } }; + Assertions.assertArrayEquals(expect2, ArrayUtil.toStringMatrixArray(output2)); + } + + static class Solution { + + private List> res; + + // 输入棋盘边长 n,返回所有合法的放置 + public List> solveNQueens(int n) { + res = new ArrayList<>(); + // '.' 表示空,'Q' 表示皇后,初始化空棋盘。 + char[] arr = new char[n]; + Arrays.fill(arr, '.'); + String str = new String(arr); + List board = new ArrayList<>(); + for (int i = 0; i < n; i++) { + board.add(str); + } + backtrack(board, 0); + return res; + } + + // 路径:board 中小于 row 的那些行都已经成功放置了皇后 + // 选择列表:第 row 行的所有列都是放置皇后的选择 + // 结束条件:row 超过 board 的最后一行 + public void backtrack(List board, int row) { + // 触发结束条件 + if (row == board.size()) { + res.add(new ArrayList<>(board)); + return; + } + + int n = board.get(row).length(); + for (int col = 0; col < n; col++) { + // 排除不合法选择 + if (!isValid(board, row, col)) { + continue; + } + // 做选择 + char[] newRow = board.get(row).toCharArray(); + newRow[col] = 'Q'; + board.set(row, new String(newRow)); + // 进入下一行决策 + backtrack(board, row + 1); + // 撤销选择 + newRow[col] = '.'; + board.set(row, new String(newRow)); + } + } + + // 是否可以在 board[row][col] 放置皇后? + public boolean isValid(List board, int row, int col) { + int n = board.size(); + // 检查列是否有皇后互相冲突 + for (int i = 0; i < row; i++) { + if (board.get(i).charAt(col) == 'Q') { return false; } + } + // 检查右上方是否有皇后互相冲突 + for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { + if (board.get(i).charAt(j) == 'Q') { return false; } + } + // 检查左上方是否有皇后互相冲突 + for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { + if (board.get(i).charAt(j) == 'Q') { return false; } + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\2162.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\2162.java" new file mode 100644 index 0000000..c9a72cf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/N\347\232\207\345\220\2162.java" @@ -0,0 +1,94 @@ +package io.github.dunwu.algorithm.dfs.sudoku; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 52. N皇后II + * + * @author Zhang Peng + * @since 2020-07-04 + */ +public class N皇后2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.totalNQueens(4)); + Assertions.assertEquals(1, s.totalNQueens(1)); + } + + static class Solution { + + private List> res; + + public int totalNQueens(int n) { + return solveNQueens(n).size(); + } + + // 输入棋盘边长 n,返回所有合法的放置 + public List> solveNQueens(int n) { + res = new ArrayList<>(); + // '.' 表示空,'Q' 表示皇后,初始化空棋盘。 + char[] arr = new char[n]; + Arrays.fill(arr, '.'); + String str = new String(arr); + List board = new ArrayList<>(); + for (int i = 0; i < n; i++) { + board.add(str); + } + backtrack(board, 0); + return res; + } + + // 路径:board 中小于 row 的那些行都已经成功放置了皇后 + // 选择列表:第 row 行的所有列都是放置皇后的选择 + // 结束条件:row 超过 board 的最后一行 + public void backtrack(List board, int row) { + // 触发结束条件 + if (row == board.size()) { + res.add(new ArrayList<>(board)); + return; + } + + int n = board.get(row).length(); + for (int col = 0; col < n; col++) { + // 排除不合法选择 + if (!isValid(board, row, col)) { + continue; + } + // 做选择 + char[] newRow = board.get(row).toCharArray(); + newRow[col] = 'Q'; + board.set(row, new String(newRow)); + // 进入下一行决策 + backtrack(board, row + 1); + // 撤销选择 + newRow[col] = '.'; + board.set(row, new String(newRow)); + } + } + + // 是否可以在 board[row][col] 放置皇后? + public boolean isValid(List board, int row, int col) { + int n = board.size(); + // 检查列是否有皇后互相冲突 + for (int i = 0; i < row; i++) { + if (board.get(i).charAt(col) == 'Q') { return false; } + } + // 检查右上方是否有皇后互相冲突 + for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { + if (board.get(i).charAt(j) == 'Q') { return false; } + } + // 检查左上方是否有皇后互相冲突 + for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { + if (board.get(i).charAt(j) == 'Q') { return false; } + } + return true; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/package-info.java new file mode 100644 index 0000000..34ca619 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/package-info.java @@ -0,0 +1,7 @@ +/** + * 回溯算法解数独、N 皇后问题 + * + * @author Zhang Peng + * @date 2025-12-13 + */ +package io.github.dunwu.algorithm.dfs.sudoku; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/\350\247\243\346\225\260\347\213\254.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/\350\247\243\346\225\260\347\213\254.java" new file mode 100644 index 0000000..3a198c3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/sudoku/\350\247\243\346\225\260\347\213\254.java" @@ -0,0 +1,249 @@ +package io.github.dunwu.algorithm.dfs.sudoku; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 37. 解数独 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 解数独 { + + public static void main(String[] args) { + + char[][] expect = { + { '5', '3', '4', '6', '7', '8', '9', '1', '2' }, + { '6', '7', '2', '1', '9', '5', '3', '4', '8' }, + { '1', '9', '8', '3', '4', '2', '5', '6', '7' }, + { '8', '5', '9', '7', '6', '1', '4', '2', '3' }, + { '4', '2', '6', '8', '5', '3', '7', '9', '1' }, + { '7', '1', '3', '9', '2', '4', '8', '5', '6' }, + { '9', '6', '1', '5', '3', '7', '2', '8', '4' }, + { '2', '8', '7', '4', '1', '9', '6', '3', '5' }, + { '3', '4', '5', '2', '8', '6', '1', '7', '9' } + }; + char[][] input = { + { '5', '3', '.', '.', '7', '.', '.', '.', '.' }, + { '6', '.', '.', '1', '9', '5', '.', '.', '.' }, + { '.', '9', '8', '.', '.', '.', '.', '6', '.' }, + { '8', '.', '.', '.', '6', '.', '.', '.', '3' }, + { '4', '.', '.', '8', '.', '3', '.', '.', '1' }, + { '7', '.', '.', '.', '2', '.', '.', '.', '6' }, + { '.', '6', '.', '.', '.', '.', '2', '8', '.' }, + { '.', '.', '.', '4', '1', '9', '.', '.', '5' }, + { '.', '.', '.', '.', '8', '.', '.', '7', '9' } + }; + char[][] input2 = { + { '5', '3', '.', '.', '7', '.', '.', '.', '.' }, + { '6', '.', '.', '1', '9', '5', '.', '.', '.' }, + { '.', '9', '8', '.', '.', '.', '.', '6', '.' }, + { '8', '.', '.', '.', '6', '.', '.', '.', '3' }, + { '4', '.', '.', '8', '.', '3', '.', '.', '1' }, + { '7', '.', '.', '.', '2', '.', '.', '.', '6' }, + { '.', '6', '.', '.', '.', '.', '2', '8', '.' }, + { '.', '.', '.', '4', '1', '9', '.', '.', '5' }, + { '.', '.', '.', '.', '8', '.', '.', '7', '9' } + }; + + Solution s = new Solution(); + s.solveSudoku(input); + Assertions.assertArrayEquals(expect, input); + + Solution2 s2 = new Solution2(); + s2.solveSudoku(input2); + Assertions.assertArrayEquals(expect, input2); + } + + static class Solution { + + private int n; + // 标记是否已经找到可行解 + boolean found = false; + + public void solveSudoku(char[][] board) { + n = board.length; + backtrack(board, 0); + } + + // 路径:board 中小于 index 的位置所填的数字 + // 选择列表:数字 1~9 + // 结束条件:整个 board 都填满数字 + void backtrack(char[][] board, int index) { + if (found) { + // 已经找到一个可行解,立即结束 + return; + } + + int[] point = point(index); + int row = point[0], col = point[1]; + if (index == n * n) { + // 找到一个可行解,触发 base case + found = true; + return; + } + + if (board[row][col] != '.') { + // 如果有预设数字,不用我们穷举 + backtrack(board, index + 1); + return; + } + + for (char ch = '1'; ch <= '9'; ch++) { + // 剪枝:如果遇到不合法的数字,就跳过 + if (!isValid(board, row, col, ch)) { continue; } + + // 做选择 + board[row][col] = ch; + + backtrack(board, index + 1); + if (found) { + // 如果找到一个可行解,立即结束 + // 不要撤销选择,否则 board[i][j] 会被重置为 '.' + return; + } + + // 撤销选择 + board[row][col] = '.'; + } + } + + // 判断是否可以在 (r, c) 位置放置数字 num + boolean isValid(char[][] board, int row, int col, char num) { + for (int i = 0; i < 9; i++) { + // 判断行是否存在重复 + if (board[row][i] == num) return false; + // 判断列是否存在重复 + if (board[i][col] == num) return false; + // 判断 3 x 3 方框是否存在重复 + if (board[(row / 3) * 3 + i / 3][(col / 3) * 3 + i % 3] == num) { return false; } + } + return true; + } + + public int index(int x, int y) { + return x * n + y; + } + + public int[] point(int index) { + int x = index / n; + int y = index % n; + return new int[] { x, y }; + } + + } + + static class Solution2 { + + private int n; + + // 标记是否已经找到可行解 + private boolean found; + + // 记录每行已经出现的数字 + // 比如 rows[0] = {1, 2, 3} 表示第 0 行已经出现了数字 1, 2, 3 + private final List> rows; + + // 记录每列已经出现的数字 + private final List> cols; + + // 记录每个九宫格已经出现的数字 + private final List> boxes; + + public Solution2() { + found = false; + rows = new ArrayList<>(9); + cols = new ArrayList<>(9); + boxes = new ArrayList<>(9); + for (int i = 0; i < 9; i++) { + rows.add(new HashSet<>()); + cols.add(new HashSet<>()); + boxes.add(new HashSet<>()); + } + } + + public void solveSudoku(char[][] board) { + n = board.length; + // 将预设数字加入集合 + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + if (board[i][j] != '.') { + rows.get(i).add(board[i][j]); + cols.get(j).add(board[i][j]); + boxes.get(getBoxIndex(i, j)).add(board[i][j]); + } + } + } + backtrack(board, 0); + } + + // 路径:board 中小于 index 的位置所填的数字 + // 选择列表:数字 1~9 + // 结束条件:整个 board 都填满数字 + public void backtrack(char[][] board, int index) { + + // 已经找到一个可行解,立即结束 + if (found) { return; } + + // 找到一个可行解,触发 base case + if (index == n * n) { + found = true; + return; + } + + int row = index / n; + int col = index % n; + // 如果有预设数字,无需穷举 + if (board[row][col] != '.') { + backtrack(board, index + 1); + return; + } + + for (char ch = '1'; ch <= '9'; ch++) { + + // 【剪枝】如果遇到不合法的数字,就跳过 + if (!isValid(row, col, ch)) { continue; } + + // 【选择】把 ch 填入 board[i][j] + board[row][col] = ch; + rows.get(row).add(ch); + cols.get(col).add(ch); + boxes.get(getBoxIndex(row, col)).add(ch); + + backtrack(board, index + 1); + if (found) { + // 如果找到一个可行解,立即结束 + // 不要撤销选择,否则 board[i][j] 会被重置为 '.' + return; + } + + // 【取消选择】把 board[i][j] 重置为 '.' + board[row][col] = '.'; + rows.get(row).remove(ch); + cols.get(col).remove(ch); + boxes.get(getBoxIndex(row, col)).remove(ch); + } + } + + // 获取 (row, col) 所在的九宫格索引 + public int getBoxIndex(int row, int col) { + return (row / 3) * 3 + (col / 3); + } + + // 判断是否可以在 (row, col) 位置放置数字 num + public boolean isValid(int row, int col, char num) { + // 现在只需要查询三次哈希表即可 + if (rows.get(row).contains(num)) { return false; } + if (cols.get(col).contains(num)) { return false; } + if (boxes.get(getBoxIndex(row, col)).contains(num)) { return false; } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/template/\345\233\236\346\272\257\347\256\227\346\263\225\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/template/\345\233\236\346\272\257\347\256\227\346\263\225\346\250\241\346\235\277.java" new file mode 100644 index 0000000..24b5dd5 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/template/\345\233\236\346\272\257\347\256\227\346\263\225\346\250\241\346\235\277.java" @@ -0,0 +1,20 @@ +package io.github.dunwu.algorithm.dfs.template; + +/** + * 回溯算法模板 + * + * @author Zhang Peng + * @date 2025-12-11 + */ +public class 回溯算法模板 { + + // 【回溯算法伪代码模板】 + // for 选择 in 选择列表: + // # 做选择 + // 将该选择从选择列表移除 + // 路径.add(选择) + // backtrack(路径, 选择列表) + // # 撤销选择 + // 路径.remove(选择) + // 将该选择再加入选择列表 +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\344\274\230\347\276\216\347\232\204\346\216\222\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\344\274\230\347\276\216\347\232\204\346\216\222\345\210\227.java" new file mode 100644 index 0000000..cb4aff8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\344\274\230\347\276\216\347\232\204\346\216\222\345\210\227.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 526. 优美的排列 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 优美的排列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.countArrangement(1)); + Assertions.assertEquals(2, s.countArrangement(2)); + } + + static class Solution { + + // 记录所有的「优美排列」的个数 + private int res = 0; + // track 中的元素会被标记为 true,避免重复选择 + private boolean[] visited; + // 记录回溯算法的递归路径,即每个索引选择的元素 + private LinkedList path; + + public int countArrangement(int n) { + res = 0; + visited = new boolean[n + 1]; + path = new LinkedList<>(); + dfs(n, 1); + return res; + } + + // 回溯算法标准框架,站在索引的视角选择元素 + void dfs(int n, int index) { + // base case,到达叶子节点 + if (index > n) { + // 找到一个结果 + res += 1; + return; + } + + // 索引 index 开始选择元素 + for (int val = 1; val <= n; val++) { + // 已经被其他索引选过的元素,不能重复选择 + if (visited[val]) { + continue; + } + if (!(index % val == 0 || val % index == 0)) { + continue; + } + // 【选择】index 选择元素 elem + visited[val] = true; + path.addLast(val); + // 【回溯】 + dfs(n, index + 1); + // 【取消选择】 + path.removeLast(); + visited[val] = false; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.java" new file mode 100644 index 0000000..7c63a40 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\210\206\345\211\262\345\233\236\346\226\207\344\270\262.java" @@ -0,0 +1,72 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 131. 分割回文串 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 分割回文串 { + + public static void main(String[] args) { + Solution s = new Solution(); + + String[][] expect = new String[][] { { "a", "a", "b" }, { "aa", "b" } }; + List> output = s.partition("aab"); + Assertions.assertEquals(expect.length, output.size()); + + List> output2 = s.partition("a"); + Assertions.assertEquals(1, output2.size()); + } + + static class Solution { + + private List path; + private List> res; + + public List> partition(String s) { + path = new LinkedList<>(); + res = new LinkedList<>(); + dfs(s, 0); + return res; + } + + public void dfs(String s, int start) { + if (start == s.length()) { + // base case,走到叶子节点 + // 即整个 s 被成功分割为若干个回文子串,记下答案 + res.add(new LinkedList(path)); + } + for (int i = start; i < s.length(); i++) { + if (!isPalindrome(s, start, i)) { + // s[start..i] 不是回文串,不能分割 + continue; + } + // s[start..i] 是一个回文串,可以进行分割 + // 做选择,把 s[start..i] 放入路径列表中 + path.add(s.substring(start, i + 1)); + // 进入回溯树的下一层,继续切分 s[i+1..] + dfs(s, i + 1); + // 撤销选择 + path.remove(path.size() - 1); + } + } + + // 用双指针技巧判断 s[low..high] 是否是一个回文串 + public boolean isPalindrome(String s, int low, int high) { + while (low < high) { + if (s.charAt(low) != s.charAt(high)) { return false; } + low++; + high--; + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\215\225\350\257\215\346\220\234\347\264\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\215\225\350\257\215\346\220\234\347\264\242.java" new file mode 100644 index 0000000..ef11cfe --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\215\225\350\257\215\346\220\234\347\264\242.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +/** + * 79. 单词搜索 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 单词搜索 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue( + s.exist(new char[][] { { 'A', 'B', 'C', 'E' }, { 'S', 'F', 'C', 'S' }, { 'A', 'D', 'E', 'E' } }, "ABCCED")); + Assertions.assertTrue( + s.exist(new char[][] { { 'A', 'B', 'C', 'E' }, { 'S', 'F', 'C', 'S' }, { 'A', 'D', 'E', 'E' } }, "SEE")); + Assertions.assertFalse( + s.exist(new char[][] { { 'A', 'B', 'C', 'E' }, { 'S', 'F', 'C', 'S' }, { 'A', 'D', 'E', 'E' } }, "ABCB")); + } + + static class Solution { + + boolean found = false; + + public boolean exist(char[][] board, String word) { + found = false; + int m = board.length, n = board[0].length; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + dfs(board, i, j, word, 0); + if (found) { return true; } + } + } + return false; + } + + // 从 (i, j) 开始向四周搜索,试图匹配 word[p..] + void dfs(char[][] board, int i, int j, String word, int p) { + if (p == word.length()) { + // 整个 word 已经被匹配完,找到了一个答案 + found = true; + return; + } + if (found) { + // 已经找到了一个答案,不用再搜索了 + return; + } + int m = board.length, n = board[0].length; + if (i < 0 || j < 0 || i >= m || j >= n) { + return; + } + if (board[i][j] != word.charAt(p)) { + return; + } + + // 已经匹配过的字符,我们给它添一个负号作为标记,避免走回头路 + board[i][j] = (char) (-board[i][j]); + // word[p] 被 board[i][j] 匹配,开始向四周搜索 word[p+1..] + dfs(board, i + 1, j, word, p + 1); + dfs(board, i, j + 1, word, p + 1); + dfs(board, i - 1, j, word, p + 1); + dfs(board, i, j - 1, word, p + 1); + board[i][j] = (char) (-board[i][j]); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" new file mode 100644 index 0000000..9799f09 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +/** + * https://leetcode-cn.com/problems/reverse-string/ + * + * @author Zhang Peng + * @since 2020-07-08 + */ +public class 反转字符串 { + + public static void main(String[] args) { + char[] str = { 'h', 'e', 'l', 'l', 'o' }; + reverseString(str); + Assertions.assertArrayEquals(new char[] { 'o', 'l', 'l', 'e', 'h' }, str); + + char[] str2 = { 'H', 'a', 'n', 'n', 'a', 'h' }; + reverseString(str2); + Assertions.assertArrayEquals(new char[] { 'h', 'a', 'n', 'n', 'a', 'H' }, str2); + } + + public static void reverseString(char[] s) { + if (s == null || s.length == 0) return; + int l = 0, r = s.length - 1; + recursive(s, l, r); + } + + private static void recursive(char[] s, int l, int r) { + if (l >= r) return; + char temp = s[l]; + s[l] = s[r]; + s[r] = temp; + recursive(s, l + 1, r - 1); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\244\215\345\216\237IP\345\234\260\345\235\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\244\215\345\216\237IP\345\234\260\345\235\200.java" new file mode 100644 index 0000000..ce16a6a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\345\244\215\345\216\237IP\345\234\260\345\235\200.java" @@ -0,0 +1,88 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 93. 复原 IP 地址 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 复原IP地址 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new String[] { "255.255.11.135", "255.255.111.35" }, + s.restoreIpAddresses("25525511135").toArray()); + Assertions.assertArrayEquals(new String[] { "0.0.0.0" }, s.restoreIpAddresses("0000").toArray()); + Assertions.assertArrayEquals(new String[] { "1.0.10.23", "1.0.102.3", "10.1.0.23", "10.10.2.3", "101.0.2.3" }, + s.restoreIpAddresses("101023").toArray()); + } + + static class Solution { + + private LinkedList path; + private LinkedList res; + + public List restoreIpAddresses(String s) { + path = new LinkedList<>(); + res = new LinkedList<>(); + dfs(s, 0); + return res; + } + + public void dfs(String s, int start) { + + // base case,走到叶子节点 + // 即整个 s 被成功分割为合法的四部分,记下答案 + if (start == s.length() && path.size() == 4) { + String ip = String.join(".", path); + res.add(ip); + } + + for (int i = start; i < s.length(); i++) { + + // s[start..i] 不是合法的 ip 数字,不能分割 + if (!isValid(s, start, i)) { continue; } + + // 已经分解成 4 部分了,不能再分解了 + if (path.size() >= 4) { continue; } + + // 【选择】 + // s[start..i] 是一个合法的 ip 数字,可以进行分割 + // 做选择,把 s[start..i] 放入路径列表中 + path.add(s.substring(start, i + 1)); + + // 【回溯】 + dfs(s, i + 1); + + // 【取消选择】 + path.removeLast(); + } + } + + public boolean isValid(String s, int start, int end) { + + int length = end - start + 1; + + if (length == 0 || length > 3) { return false; } + + // 如果只有一位数字,肯定是合法的 + if (length == 1) { return true; } + + // 多于一位数字,但开头是 0,肯定不合法 + if (s.charAt(start) == '0') { return false; } + + // 排除了开头是 0 的情况,那么如果是两位数,肯定是合法的 + if (length <= 2) { return true; } + + // 现在输入的一定是三位数,不能大于 255 + return Integer.parseInt(s.substring(start, start + length)) <= 255; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\346\240\274\351\233\267\347\274\226\347\240\201.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\346\240\274\351\233\267\347\274\226\347\240\201.java" new file mode 100644 index 0000000..92162c4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\346\240\274\351\233\267\347\274\226\347\240\201.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.dfs; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 526. 优美的排列 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 格雷编码 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, ArrayUtil.toIntArray(s.grayCode(1))); + Assertions.assertArrayEquals(new int[] { 0, 1, 3, 2 }, ArrayUtil.toIntArray(s.grayCode(2))); + } + + static class Solution { + + private boolean[] visited; + private LinkedList path; + private LinkedList res; + + public List grayCode(int n) { + visited = new boolean[n]; + path = new LinkedList<>(); + res = null; + dfs(n, 0); + return res; + } + + public void dfs(int n, int root) { + if (res != null) return; + if (path.size() == (1 << n)) { + res = new LinkedList<>(path); + return; + } + if (visited[root]) return; + + visited[root] = true; + path.addLast(root); + + for (int i = 0; i < n; i++) { + int next = flipBit(root, i); + dfs(n, next); + } + + path.removeLast(); + visited[root] = false; + } + + // 把第 i 位取反(0 变 1,1 变 0) + int flipBit(int x, int i) { + return x ^ (1 << i); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\345\255\227\346\257\215\347\273\204\345\220\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\345\255\227\346\257\215\347\273\204\345\220\210.java" new file mode 100644 index 0000000..def2aea --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\347\224\265\350\257\235\345\217\267\347\240\201\347\232\204\345\255\227\346\257\215\347\273\204\345\220\210.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 17. 电话号码的字母组合 + * + * @author Zhang Peng + * @date 2025-11-05 + */ +public class 电话号码的字母组合 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new String[] { "ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf" }, + s.letterCombinations("23").toArray()); + Assertions.assertArrayEquals(new String[] { "a", "b", "c" }, s.letterCombinations("2").toArray()); + Assertions.assertArrayEquals(new String[] { "t", "u", "v" }, s.letterCombinations("8").toArray()); + } + + static class Solution { + + private StringBuilder sb; + private LinkedList res; + private final String[] options = new String[] { + "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" + }; + + public List letterCombinations(String digits) { + if (digits.isEmpty()) { return new LinkedList<>(); } + sb = new StringBuilder(); + res = new LinkedList<>(); + // 从 digits[0] 开始进行回溯 + dfs(digits, 0); + return res; + } + + // 回溯算法主函数 + public void dfs(String digits, int start) { + // 到达回溯树底部 + if (sb.length() == digits.length()) { + res.add(sb.toString()); + return; + } + + // 回溯算法框架 + int digit = digits.charAt(start) - '0'; + for (char c : options[digit].toCharArray()) { + sb.append(c); + dfs(digits, start + 1); + sb.deleteCharAt(sb.length() - 1); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\350\277\236\347\273\255\345\267\256\347\233\270\345\220\214\347\232\204\346\225\260\345\255\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\350\277\236\347\273\255\345\267\256\347\233\270\345\220\214\347\232\204\346\225\260\345\255\227.java" new file mode 100644 index 0000000..d9a0551 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\350\277\236\347\273\255\345\267\256\347\233\270\345\220\214\347\232\204\346\225\260\345\255\227.java" @@ -0,0 +1,78 @@ +package io.github.dunwu.algorithm.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 967. 连续差相同的数字 + * + * @author Zhang Peng + * @date 2025-11-05 + */ +public class 连续差相同的数字 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 181, 292, 707, 818, 929 }, s.numsSameConsecDiff(3, 7)); + Assertions.assertArrayEquals(new int[] { 10, 12, 21, 23, 32, 34, 43, 45, 54, 56, 65, 67, 76, 78, 87, 89, 98 }, + s.numsSameConsecDiff(2, 1)); + Assertions.assertArrayEquals(new int[] { 11, 22, 33, 44, 55, 66, 77, 88, 99 }, s.numsSameConsecDiff(2, 0)); + Assertions.assertArrayEquals(new int[] { 13, 20, 24, 31, 35, 42, 46, 53, 57, 64, 68, 75, 79, 86, 97 }, + s.numsSameConsecDiff(2, 2)); + } + + static class Solution { + + // 记录当前路径组成的数字的值 + private int num = 0; + // 记录当前数字的位数 + private int digit = 0; + private List res; + + public int[] numsSameConsecDiff(int n, int k) { + + num = 0; + digit = 0; + res = new LinkedList<>(); + + dfs(n, k); + + int[] arr = new int[res.size()]; + for (int i = 0; i < res.size(); i++) { + arr[i] = res.get(i); + } + return arr; + } + + // 回溯算法核心函数 + void dfs(int n, int k) { + // base case,到达叶子节点 + if (digit == n) { + // 找到一个合法的 n 位数 + res.add(num); + return; + } + + // 回溯算法标准框架 + for (int i = 0; i <= 9; i++) { + // 本题的剪枝逻辑 1,第一个数字不能是 0 + if (digit == 0 && i == 0) continue; + // 本题的剪枝逻辑 2,相邻两个数字的差的绝对值必须等于 k + if (digit > 0 && Math.abs(i - num % 10) != k) continue; + + // 做选择,在 track 尾部追加数字 i + digit++; + num = 10 * num + i; + // 进入下一层回溯树 + dfs(n, k); + // 取消选择,删除 track 尾部数字 + num = num / 10; + digit--; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\351\235\236\351\200\222\345\207\217\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\351\235\236\351\200\222\345\207\217\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..0c5494d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dfs/\351\235\236\351\200\222\345\207\217\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.dfs; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 491. 非递减子序列 + * + * @author Zhang Peng + * @date 2025-12-12 + */ +public class 非递减子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + + int[][] expect = new int[][] { + { 4, 6 }, { 4, 6, 7 }, { 4, 6, 7, 7 }, { 4, 7 }, { 4, 7, 7 }, { 6, 7 }, { 6, 7, 7 }, { 7, 7 } + }; + List> output = s.findSubsequences(new int[] { 4, 6, 7, 7 }); + Assertions.assertArrayEquals(expect, ArrayUtil.toIntMatrixArray(output)); + + List> output2 = s.findSubsequences(new int[] { 4, 4, 3, 2, 1 }); + Assertions.assertArrayEquals(new int[][] { { 4, 4 } }, ArrayUtil.toIntMatrixArray(output2)); + } + + static class Solution { + + private List path; + private List> res; + + public List> findSubsequences(int[] nums) { + + // base case + if (nums == null || nums.length == 0) { return new LinkedList<>(); } + + path = new LinkedList<>(); + res = new LinkedList<>(); + + dfs(nums, 0); + return res; + } + + public void dfs(int[] nums, int start) { + if (path.size() >= 2) { + res.add(new LinkedList<>(path)); + } + + // 用哈希集合防止重复选择相同元素 + HashSet visited = new HashSet<>(); + + for (int i = start; i < nums.length; i++) { + + if (!path.isEmpty() && nums[i] < path.get(path.size() - 1)) { continue; } + // 保证不要重复使用相同的元素 + if (visited.contains(nums[i])) { continue; } + + // 【选择】 + visited.add(nums[i]); + path.add(nums[i]); + + // 【递归】 + dfs(nums, i + 1); + + // 【取消选择】 + path.remove(path.size() - 1); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/N\346\254\241\345\271\202.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/N\346\254\241\345\271\202.java" new file mode 100644 index 0000000..80a3464 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/N\346\254\241\345\271\202.java" @@ -0,0 +1,75 @@ +package io.github.dunwu.algorithm.divide; + +import org.junit.jupiter.api.Assertions; + +/** + * 50. Pow(x, n) 求 N次幂 + * + * @author Zhang Peng + * @see 50. Pow(x, n) + * @since 2020-07-02 + */ +public class N次幂 { + + public static void main(String[] args) { + Assertions.assertEquals(1024.00000, myPow(2.00000, 10)); + Assertions.assertEquals(9.261000000000001, myPow(2.10000, 3)); + Assertions.assertEquals(0.25000, myPow(2.00000, -2)); + + Assertions.assertEquals(1024.00000, myPow2(2.00000, 10)); + Assertions.assertEquals(9.261000000000001, myPow2(2.10000, 3)); + Assertions.assertEquals(0.25000, myPow2(2.00000, -2)); + } + + /** + * 分治算法 + *

+ * x^n 可以视为 y^2(n为偶数) 或 x*y^2(n为奇数) + *

+ * 时间复杂度:O(log N) + */ + public static double myPow(double x, int n) { + if (n > 0) return helper(x, n); + return 1.00000 / helper(x, -n); + } + + public static double helper(double x, int n) { + if (n == 0) { + return 1.00000; + } + + double y = helper(x, n / 2); + if (n % 2 == 0) { + return y * y; + } else { + return x * y * y; + } + } + + /** + * 暴力破解法 + *

+ * 时间复杂度:O(N) + */ + public static double myPow2(double x, int n) { + double result = 1.00000; + + if (n == 0) { + return 1.00000; + } + + int cnt = n > 0 ? n : -n; + for (int i = 0; i < cnt; i++) { + result *= x; + } + + if (n < 0) result = 1.00000 / result; + return result; + } + + // 库函数 + public static double myPow3(double x, int n) { + return Math.pow(x, n); + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/package-info.java new file mode 100644 index 0000000..f051be8 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/package-info.java @@ -0,0 +1,7 @@ +/** + * 分治算法 + * + * @author Zhang Peng + * @since 2020-07-02 + */ +package io.github.dunwu.algorithm.divide; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/\345\244\232\346\225\260\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/\345\244\232\346\225\260\345\205\203\347\264\240.java" new file mode 100644 index 0000000..a93d092 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/divide/\345\244\232\346\225\260\345\205\203\347\264\240.java" @@ -0,0 +1,48 @@ +package io.github.dunwu.algorithm.divide; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 多数元素 + * + * @author Zhang Peng + * @see 多数元素 + * @since 2020-07-02 + */ +public class 多数元素 { + + public static void main(String[] args) { + Assertions.assertEquals(3, majorityElement(new int[] { 3, 2, 3 })); + Assertions.assertEquals(2, majorityElement(new int[] { 2, 2, 1, 1, 1, 2, 2 })); + Assertions.assertEquals(6, majorityElement(new int[] { 6, 6, 6, 7, 7 })); + } + + // 暴力破解法 + // 时间复杂度:O(N) + O(log N) + public static int majorityElement(int[] nums) { + Arrays.sort(nums); + int max = 1; + int count = 0; + int currElem = nums[0]; + int maxElem = nums[0]; + for (int i = 0; i < nums.length; i++) { + if (nums[i] != currElem) { + count = 1; + currElem = nums[i]; + } else { + count++; + if (maxElem == currElem) { + max = count; + } else { + if (max < count) { + maxElem = currElem; + } + } + } + } + return maxElem; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2602.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2602.java" new file mode 100644 index 0000000..dcb4260 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2602.java" @@ -0,0 +1,42 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +/** + * 264. 丑数II + * + * @author Zhang Peng + * @date 2025-01-24 + */ +public class 丑数2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(12, s.nthUglyNumber(10)); + Assertions.assertEquals(1, s.nthUglyNumber(1)); + Assertions.assertEquals(15, s.nthUglyNumber(11)); + } + + static class Solution { + + public int nthUglyNumber(int n) { + int[] dp = new int[n + 1]; + dp[1] = 1; + int p2 = 1, p3 = 1, p5 = 1; + for (int index = 2; index <= n; index++) { + int n2 = dp[p2] * 2, n3 = dp[p3] * 3, n5 = dp[p5] * 5; + dp[index] = min(n2, n3, n5); + if (dp[index] == n2) { p2++; } + if (dp[index] == n3) { p3++; } + if (dp[index] == n5) { p5++; } + } + return dp[n]; + } + + public int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2603.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2603.java" new file mode 100644 index 0000000..1ca3fb6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\344\270\221\346\225\2603.java" @@ -0,0 +1,42 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +/** + * 1201. 丑数 III + * + * @author Zhang Peng + * @date 2025-01-24 + */ +public class 丑数3 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.nthUglyNumber(3, 2, 3, 5)); + Assertions.assertEquals(6, s.nthUglyNumber(4, 2, 3, 4)); + Assertions.assertEquals(10, s.nthUglyNumber(5, 2, 11, 13)); + } + + static class Solution { + + public int nthUglyNumber(int n, int a, int b, int c) { + int[] dp = new int[n + 1]; + int pa = 1, pb = 1, pc = 1; + dp[0] = 1; + for (int i = 1; i <= n; i++) { + int na = pa * a, nb = pb * b, nc = pc * c; + dp[i] = min(na, nb, nc); + if (dp[i] == na) { pa++; } + if (dp[i] == nb) { pb++; } + if (dp[i] == nc) { pc++; } + } + return dp[n]; + } + + int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\346\234\200\344\275\216\347\245\250\344\273\267.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\346\234\200\344\275\216\347\245\250\344\273\267.java" new file mode 100644 index 0000000..80c922b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\346\234\200\344\275\216\347\245\250\344\273\267.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +/** + * 983. 最低票价 + * + * @author Zhang Peng + * @since 2025-11-17 + */ +public class 最低票价 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(11, s.mincostTickets(new int[] { 1, 4, 6, 7, 8, 20 }, new int[] { 2, 7, 15 })); + Assertions.assertEquals(17, + s.mincostTickets(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 31 }, new int[] { 2, 7, 15 })); + } + + // 动态规划 + static class Solution { + + public int mincostTickets(int[] days, int[] costs) { + int n = days.length; + int lastDay = days[n - 1]; + boolean[] isTravel = new boolean[lastDay + 1]; + for (int d : days) { isTravel[d] = true; } + + // dp[i] 表示 1 到 i 天的最小花费 + int[] dp = new int[lastDay + 1]; + dp[0] = 0; + for (int i = 1; i <= lastDay; i++) { + if (!isTravel[i]) { + // 如果第 i 天不在 days 中,则第 i 天和第 i - 1 天花费相同 + dp[i] = dp[i - 1]; + } else { + // 如果第 i 天在 days 中 + // 则求三种不同方案最小值: + dp[i] = min( + // 在第 i 天购买为期 1 天的通行证的最小花费 + costs[0] + dp[i - 1], + // 在第 i - 7 天购买为期 7 天的通行证的最小花费(如果 i - 7 < 0,视为 0,f[0] 花费为 0) + costs[1] + dp[Math.max(0, i - 7)], + // 在第 i - 30 天购买为期 30 天的通行证的最小花费(如果 i - 30 < 0,视为 0,f[0] 花费为 0) + costs[2] + dp[Math.max(0, i - 30)] + ); + } + } + return dp[lastDay]; + } + + public int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\347\273\237\350\256\241\346\236\204\351\200\240\345\245\275\345\255\227\347\254\246\344\270\262\347\232\204\346\226\271\346\241\210\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\347\273\237\350\256\241\346\236\204\351\200\240\345\245\275\345\255\227\347\254\246\344\270\262\347\232\204\346\226\271\346\241\210\346\225\260.java" new file mode 100644 index 0000000..107feec --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\347\273\237\350\256\241\346\236\204\351\200\240\345\245\275\345\255\227\347\254\246\344\270\262\347\232\204\346\226\271\346\241\210\346\225\260.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +/** + * 2466. 统计构造好字符串的方案数 + * + * @author Zhang Peng + * @since 2025-11-17 + */ +public class 统计构造好字符串的方案数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(8, s.countGoodStrings(3, 3, 1, 1)); + Assertions.assertEquals(5, s.countGoodStrings(2, 3, 1, 2)); + } + + static class Solution { + + public int countGoodStrings(int low, int high, int zero, int one) { + final int MOD = 1_000_000_007; + int res = 0; + // dp[i] 表示构造长为 i 的字符串的方案数 + int[] dp = new int[high + 1]; + // 构造空串的方案数为 1 + dp[0] = 1; + for (int i = 1; i <= high; i++) { + if (i >= zero) dp[i] = dp[i - zero]; + if (i >= one) dp[i] = (dp[i] + dp[i - one]) % MOD; + if (i >= low) res = (res + dp[i]) % MOD; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.java" new file mode 100644 index 0000000..5126e91 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\345\206\263\346\231\272\345\212\233\351\227\256\351\242\230.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 2140. 解决智力问题 + * + * @author Zhang Peng + * @date 2025-11-17 + */ +public class 解决智力问题 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.mostPoints(new int[][] { { 3, 2 }, { 4, 3 }, { 4, 4 }, { 2, 5 } })); + Assertions.assertEquals(7, s.mostPoints(new int[][] { { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 } })); + } + + static class Solution { + + long[] memo; + + public long mostPoints(int[][] questions) { + if (questions == null || questions.length == 0) { return 0; } + memo = new long[questions.length + 1]; + Arrays.fill(memo, -1); + return dp(questions, 0); + } + + public long dp(int[][] questions, int i) { + if (i < 0 || i >= questions.length) { return 0L; } + if (memo[i] != -1) { return memo[i]; } + int score = questions[i][0]; + int skip = questions[i][1]; + memo[i] = Math.max( + dp(questions, i + 1), + dp(questions, i + skip + 1) + score + ); + return memo[i]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\347\240\201\346\226\271\346\263\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\347\240\201\346\226\271\346\263\225.java" new file mode 100644 index 0000000..d813925 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\350\247\243\347\240\201\346\226\271\346\263\225.java" @@ -0,0 +1,103 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 91. 解码方法 + * + * @author Zhang Peng + * @since 2025-11-17 + */ +public class 解码方法 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.numDecodings("12")); + Assertions.assertEquals(3, s.numDecodings("226")); + Assertions.assertEquals(0, s.numDecodings("06")); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(2, s2.numDecodings("12")); + Assertions.assertEquals(3, s2.numDecodings("226")); + Assertions.assertEquals(0, s2.numDecodings("06")); + } + + // 回溯解法 + static class Solution { + + private StringBuilder sb; + private LinkedList res; + + public int numDecodings(String s) { + sb = new StringBuilder(); + res = new LinkedList<>(); + backtrack(s, 0); + return res.size(); + } + + public void backtrack(String s, int start) { + + // base case,走到叶子节点 + // 即整个 s 被成功分割为合法的四部分,记下答案 + if (start == s.length()) { + res.add(sb.toString()); + return; + } + + for (int i = start; i < s.length(); i++) { + + // s[start..i] 不是合法的 ip 数字,不能分割 + char letter = getLetter(s, start, i); + if (letter == '#') { continue; } + + // 【选择】 + // s[start..i] 是一个合法的 ip 数字,可以进行分割 + // 做选择,把 s[start..i] 放入路径列表中 + sb.append(letter); + + // 【回溯】 + backtrack(s, i + 1); + + // 【取消选择】 + sb.deleteCharAt(sb.length() - 1); + } + } + + public char getLetter(String s, int begin, int end) { + int len = end - begin + 1; + if (len <= 0 || len > 2) { return '#'; } + String numStr = s.substring(begin, begin + len); + if (numStr.startsWith("0")) { return '#'; } + int num = Integer.parseInt(numStr); + if (num < 1 || num > 26) { return '#'; } + return (char) ('A' + (num - 1)); + } + + } + + // 动态规划 + static class Solution2 { + + public int numDecodings(String s) { + int n = s.length(); + s = " " + s; + char[] ch = s.toCharArray(); + int[] dp = new int[n + 1]; + dp[0] = 1; + for (int i = 1; i <= n; i++) { + // a : 代表「当前位置」单独形成 item + // b : 代表「当前位置」与「前一位置」共同形成 item + int a = ch[i] - '0', b = (ch[i - 1] - '0') * 10 + (ch[i] - '0'); + // 如果 a 属于有效值,那么 dp[i] 可以由 dp[i - 1] 转移过来 + if (1 <= a && a <= 9) dp[i] = dp[i - 1]; + // 如果 b 属于有效值,那么 dp[i] 可以由 dp[i - 2] 或者 dp[i - 1] & dp[i - 2] 转移过来 + if (10 <= b && b <= 26) dp[i] += dp[i - 2]; + } + return dp[n]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\351\233\266\351\222\261\345\205\221\346\215\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\351\233\266\351\222\261\345\205\221\346\215\242.java" new file mode 100644 index 0000000..feeb72a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/array/\351\233\266\351\222\261\345\205\221\346\215\242.java" @@ -0,0 +1,87 @@ +package io.github.dunwu.algorithm.dp.array; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 322. 零钱兑换 + * + * @author Zhang Peng + * @since 2025-11-17 + */ +public class 零钱兑换 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.coinChange(new int[] { 1, 2, 5 }, 11)); + Assertions.assertEquals(-1, s.coinChange(new int[] { 2 }, 3)); + Assertions.assertEquals(0, s.coinChange(new int[] { 1 }, 0)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(3, s2.coinChange(new int[] { 1, 2, 5 }, 11)); + Assertions.assertEquals(-1, s2.coinChange(new int[] { 2 }, 3)); + Assertions.assertEquals(0, s2.coinChange(new int[] { 1 }, 0)); + } + + static class Solution { + + int[] memo; + + public int coinChange(int[] coins, int amount) { + memo = new int[amount + 1]; + // 备忘录初始化为一个不会被取到的特殊值,代表还未被计算 + Arrays.fill(memo, -666); + + return dp(coins, amount); + } + + int dp(int[] coins, int amount) { + if (amount == 0) return 0; + if (amount < 0) return -1; + // 查备忘录,防止重复计算 + if (memo[amount] != -666) { return memo[amount]; } + + int res = Integer.MAX_VALUE; + for (int coin : coins) { + // 计算子问题的结果 + int subProblem = dp(coins, amount - coin); + + // 子问题无解则跳过 + if (subProblem == -1) continue; + // 在子问题中选择最优解,然后加一 + res = Math.min(res, subProblem + 1); + } + // 把计算结果存入备忘录 + memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res; + return memo[amount]; + } + + } + + static class Solution2 { + + public int coinChange(int[] coins, int amount) { + int[] dp = new int[amount + 1]; + // 数组大小为 amount + 1,初始值也为 amount + 1 + Arrays.fill(dp, amount + 1); + + // base case + dp[0] = 0; + // 外层 for 循环在遍历所有状态的所有取值 + for (int i = 0; i < dp.length; i++) { + // 内层 for 循环在求所有选择的最小值 + for (int coin : coins) { + // 子问题无解,跳过 + if (i - coin < 0) { + continue; + } + dp[i] = Math.min(dp[i], 1 + dp[i - coin]); + } + } + return (dp[amount] == amount + 1) ? -1 : dp[amount]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/package-info.java new file mode 100644 index 0000000..1e7cb09 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/package-info.java @@ -0,0 +1,7 @@ +/** + * 动态规划 - 斐波那契类型 + * + * @author Zhang Peng + * @date 2025-11-12 + */ +package io.github.dunwu.algorithm.dp.fib; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.java" new file mode 100644 index 0000000..e0d3ca1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\344\275\277\347\224\250\346\234\200\345\260\217\350\212\261\350\264\271\347\210\254\346\245\274\346\242\257.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +/** + * 746. 使用最小花费爬楼梯 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 使用最小花费爬楼梯 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(15, s.minCostClimbingStairs(new int[] { 10, 15, 20 })); + Assertions.assertEquals(6, s.minCostClimbingStairs(new int[] { 1, 100, 1, 1, 1, 100, 1, 1, 100, 1 })); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(15, s2.minCostClimbingStairs(new int[] { 10, 15, 20 })); + Assertions.assertEquals(6, s2.minCostClimbingStairs(new int[] { 1, 100, 1, 1, 1, 100, 1, 1, 100, 1 })); + } + + static class Solution { + + public int minCostClimbingStairs(int[] cost) { + if (cost == null || cost.length == 0) { return 0; } + int N = cost.length; + int[] dp = new int[N + 1]; + dp[0] = dp[1] = 0; + for (int i = 2; i <= N; i++) { + dp[i] = Math.min( + dp[i - 1] + cost[i - 1], + dp[i - 2] + cost[i - 2] + ); + } + return dp[N]; + } + + } + + static class Solution2 { + + public int minCostClimbingStairs(int[] cost) { + if (cost == null || cost.length == 0) { return 0; } + int pre1 = 0, pre2 = 0; + for (int i = 2; i <= cost.length; i++) { + int tmp = Math.min( + pre1 + cost[i - 1], + pre2 + cost[i - 2] + ); + pre2 = pre1; + pre1 = tmp; + } + return pre1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\345\210\240\351\231\244\345\271\266\350\216\267\345\276\227\347\202\271\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\345\210\240\351\231\244\345\271\266\350\216\267\345\276\227\347\202\271\346\225\260.java" new file mode 100644 index 0000000..2442b5b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\345\210\240\351\231\244\345\271\266\350\216\267\345\276\227\347\202\271\346\225\260.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +/** + * 740. 删除并获得点数 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 删除并获得点数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.deleteAndEarn(new int[] { 3, 4, 2 })); + // 删除 4 获得 4 个点数,因此 3 也被删除。 + // 之后,删除 2 获得 2 个点数。总共获得 6 个点数。 + Assertions.assertEquals(9, s.deleteAndEarn(new int[] { 2, 2, 3, 3, 3, 4 })); + // 删除 3 获得 3 个点数,接着要删除两个 2 和 4 。 + // 之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。 + // 总共获得 9 个点数。 + } + + static class Solution { + + public int deleteAndEarn(int[] nums) { + + if (nums == null || nums.length == 0) { return 0; } + + int n = nums.length; + int max = Integer.MIN_VALUE; + for (int i = 1; i < n; i++) { + max = Math.max(max, nums[i]); + } + + int[] sums = new int[max + 1]; + for (int num : nums) { + sums[num] += num; + } + return rob(sums); + } + + public int rob(int[] nums) { + if (nums == null || nums.length == 0) { return 0; } + if (nums.length == 1) { return nums[0]; } + int N = nums.length; + int first = nums[0], second = Math.max(nums[0], nums[1]); + for (int i = 2; i < N; i++) { + int tmp = Math.max(second, first + nums[i]); + first = second; + second = tmp; + } + return second; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\211\223\345\256\266\345\212\253\350\210\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\211\223\345\256\266\345\212\253\350\210\215.java" new file mode 100644 index 0000000..9acda1e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\211\223\345\256\266\345\212\253\350\210\215.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +/** + * 198. 打家劫舍 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 打家劫舍 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.rob(new int[] { 1, 2, 3, 1 })); + Assertions.assertEquals(12, s.rob(new int[] { 2, 7, 9, 3, 1 })); + Assertions.assertEquals(1, s.rob(new int[] { 1, 1 })); + } + + static class Solution { + + public int rob(int[] nums) { + if (nums == null || nums.length == 0) { return 0; } + if (nums.length == 1) { return nums[0]; } + int N = nums.length; + int first = nums[0], second = Math.max(nums[0], nums[1]); + for (int i = 2; i < N; i++) { + int tmp = Math.max(second, first + nums[i]); + first = second; + second = tmp; + } + return second; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260.java" new file mode 100644 index 0000000..a2bc7f4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260.java" @@ -0,0 +1,110 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 509. 斐波那契数 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 斐波那契数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.fib(2)); + Assertions.assertEquals(2, s.fib(3)); + Assertions.assertEquals(3, s.fib(4)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(1, s2.fib(2)); + Assertions.assertEquals(2, s2.fib(3)); + Assertions.assertEquals(3, s2.fib(4)); + + Solution2 s3 = new Solution2(); + Assertions.assertEquals(1, s3.fib(2)); + Assertions.assertEquals(2, s3.fib(3)); + Assertions.assertEquals(3, s3.fib(4)); + } + + /** + * 使用备忘录优化动态规划问题 + */ + static class Solution { + + int fib(int n) { + // 备忘录全初始化为 -1 + // 因为斐波那契数肯定是非负整数,所以初始化为特殊值 -1 表示未计算 + + // 因为数组的索引从 0 开始,所以需要 n + 1 个空间 + // 这样才能把 `f(0) ~ f(n)` 都记录到 memo 中 + int[] memo = new int[n + 1]; + Arrays.fill(memo, -1); + + return dp(memo, n); + } + + // 带着备忘录进行递归 + int dp(int[] memo, int n) { + // base case + if (n == 0 || n == 1) { + return n; + } + // 已经计算过,不用再计算了 + if (memo[n] != -1) { + return memo[n]; + } + // 在返回结果之前,存入备忘录 + memo[n] = dp(memo, n - 1) + dp(memo, n - 2); + return memo[n]; + } + + } + + /** + * DP Table 解决动态规划问题 + */ + static class Solution2 { + + int fib(int n) { + if (n == 0 || n == 1) { + return n; + } + + int[] dp = new int[n + 1]; + dp[0] = 0; + dp[1] = 1; + for (int i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + } + + /** + * 在 DP Table 基础上优化空间复杂度 + */ + static class Solution3 { + + int fib(int n) { + if (n == 0 || n == 1) { + return n; + } + + // 分别代表 dp[i - 1] 和 dp[i - 2] + int dp_i_1 = 1, dp_i_2 = 0; + for (int i = 2; i <= n; i++) { + // dp[i] = dp[i - 1] + dp[i - 2]; + int dp_i = dp_i_1 + dp_i_2; + dp_i_2 = dp_i_1; + dp_i_1 = dp_i; + } + return dp_i_1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\210\254\346\245\274\346\242\257.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\210\254\346\245\274\346\242\257.java" new file mode 100644 index 0000000..2b7f0bf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\210\254\346\245\274\346\242\257.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 70. 爬楼梯 + * + * @author Zhang Peng + * @since 2020-07-04 + */ +public class 爬楼梯 { + + public static void main(String[] args) { + + Solution s = new Solution(); + Assertions.assertEquals(1, s.climbStairs(0)); + Assertions.assertEquals(1, s.climbStairs(1)); + Assertions.assertEquals(2, s.climbStairs(2)); + Assertions.assertEquals(3, s.climbStairs(3)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(1, s2.climbStairs(0)); + Assertions.assertEquals(1, s2.climbStairs(1)); + Assertions.assertEquals(2, s2.climbStairs(2)); + Assertions.assertEquals(3, s2.climbStairs(3)); + } + + static class Solution { + + int[] memo = null; + + public int climbStairs(int n) { + memo = new int[n + 1]; + Arrays.fill(memo, -1); + return dp(n); + } + + // 爬第n阶楼梯的方法数量,等于 2 部分之和 + // + // 爬上 n−1 阶楼梯的方法数量。因为再爬1阶就能到第n阶 + // 爬上 n−2 阶楼梯的方法数量,因为再爬2阶就能到第n阶 + public int dp(int n) { + if (n == 0 || n == 1) return 1; + if (memo[n] != -1) return memo[n]; + memo[n] = dp(n - 1) + dp(n - 2); + return memo[n]; + } + + } + + // 空间复杂度 o(1) + static class Solution2 { + + public int climbStairs(int n) { + if (n == 0 || n == 1) return 1; + int pre1 = 1, pre2 = 1; + for (int i = 2; i <= n; i++) { + int tmp = pre1 + pre2; + pre2 = pre1; + pre1 = tmp; + } + return pre1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\254\254N\344\270\252\346\263\260\346\263\242\351\202\243\345\245\221\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\254\254N\344\270\252\346\263\260\346\263\242\351\202\243\345\245\221\346\225\260.java" new file mode 100644 index 0000000..87a3c49 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/fib/\347\254\254N\344\270\252\346\263\260\346\263\242\351\202\243\345\245\221\346\225\260.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.dp.fib; + +import org.junit.jupiter.api.Assertions; + +/** + * 1137. 第 N 个泰波那契数 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 第N个泰波那契数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.tribonacci(4)); + Assertions.assertEquals(1389537, s.tribonacci(25)); + } + + static class Solution { + + public int tribonacci(int n) { + if (n == 0) return 0; + if (n == 1 || n == 2) return 1; + + int[] dp = new int[n + 1]; + dp[0] = 0; + dp[1] = 1; + dp[2] = 1; + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]; + } + return dp[n]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/package-info.java new file mode 100644 index 0000000..60b8318 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/package-info.java @@ -0,0 +1,7 @@ +/** + * 动态规划 - 矩阵 + * + * @author Zhang Peng + * @date 2025-11-12 + */ +package io.github.dunwu.algorithm.dp.matrix; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" new file mode 100644 index 0000000..9d2e036 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\211\350\247\222\345\275\242\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; + +import java.util.List; + +/** + * 120. 三角形最小路径和 + * + * @author Zhang Peng + * @since 2020-07-04 + */ +public class 三角形最小路径和 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> input = ArrayUtil.toIntMatrixList(new int[][] { { 2 }, { 3, 4 }, { 6, 5, 7 }, { 4, 1, 8, 3 } }); + Assertions.assertEquals(11, s.minimumTotal(input)); + List> input2 = ArrayUtil.toIntMatrixList(new int[][] { { -10 } }); + Assertions.assertEquals(-10, s.minimumTotal(input2)); + } + + static class Solution { + + public int minimumTotal(List> triangle) { + + // base case + if (triangle == null || triangle.size() == 0) { return 0; } + if (triangle.size() == 1) { return triangle.get(0).get(0); } + + // 状态定义 + int n = triangle.size(); + int[][] dp = new int[n][n]; + + // 初始状态 + dp[0][0] = triangle.get(0).get(0); + + // 状态转移方程 + int min = Integer.MAX_VALUE; + for (int i = 1; i < n; i++) { + dp[i][0] = triangle.get(i).get(0) + dp[i - 1][0]; + for (int j = 1; j < i; j++) { + dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle.get(i).get(j); + } + dp[i][i] = dp[i - 1][i - 1] + triangle.get(i).get(i); + } + + for (int j = 0; j < n; j++) { + min = Math.min(min, dp[n - 1][j]); + } + return min; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\213\351\231\215\350\267\257\345\276\204\346\234\200\345\260\217\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\213\351\231\215\350\267\257\345\276\204\346\234\200\345\260\217\345\222\214.java" new file mode 100644 index 0000000..b1965bc --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\213\351\231\215\350\267\257\345\276\204\346\234\200\345\260\217\345\222\214.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 931. 下降路径最小和 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 下降路径最小和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(13, s.minFallingPathSum(new int[][] { { 2, 1, 3 }, { 6, 5, 4 }, { 7, 8, 9 } })); + Assertions.assertEquals(-59, s.minFallingPathSum(new int[][] { { -19, 57 }, { -40, -5 } })); + } + + static class Solution { + + public int minFallingPathSum(int[][] matrix) { + + // base case + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return 0; } + if (matrix.length == 1) { return matrix[0][0]; } + + // 状态定义 + int n = matrix.length; + int[][] dp = new int[n][n]; + + // 初始状态、边界状态 + for (int j = 0; j < n; j++) { + dp[0][j] = matrix[0][j]; + } + + // 状态转移 + for (int i = 1; i < n; i++) { + dp[i][0] = Math.min(dp[i - 1][0], dp[i - 1][1]) + matrix[i][0]; + for (int j = 1; j < n - 1; j++) { + dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i - 1][j + 1]) + matrix[i][j]; + } + dp[i][n - 1] = Math.min(dp[i - 1][n - 1], dp[i - 1][n - 2]) + matrix[i][n - 1]; + } + + int min = Integer.MAX_VALUE; + for (int j = 0; j < n; j++) { + min = Math.min(min, dp[n - 1][j]); + } + return min; + } + + public int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\204.java" new file mode 100644 index 0000000..aa700cf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\204.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 62. 不同路径 + * + * @author Zhang Peng + * @date 2025-11-12 + */ +public class 不同路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(28, s.uniquePaths(3, 7)); + Assertions.assertEquals(3, s.uniquePaths(3, 2)); + } + + static class Solution { + + public int uniquePaths(int m, int n) { + + // 状态定义 + int[][] dp = new int[m][n]; + + // 初始状态、边界状态 + for (int i = 0; i < m; i++) { dp[i][0] = 1; } + for (int j = 0; j < n; j++) { dp[0][j] = 1; } + + // 状态转移方程 + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + return dp[m - 1][n - 1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\2042.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\2042.java" new file mode 100644 index 0000000..50295cf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\344\270\215\345\220\214\350\267\257\345\276\2042.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 63. 不同路径 II + * + * @author Zhang Peng + * @date 2025-11-12 + */ +public class 不同路径2 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input1 = new int[][] { { 0, 0, 0 }, { 0, 1, 0 }, { 0, 0, 0 } }; + Assertions.assertEquals(2, s.uniquePathsWithObstacles(input1)); + int[][] input2 = new int[][] { { 0, 1 }, { 0, 0 } }; + Assertions.assertEquals(1, s.uniquePathsWithObstacles(input2)); + int[][] input3 = new int[][] { { 1, 0 } }; + Assertions.assertEquals(0, s.uniquePathsWithObstacles(input3)); + int[][] input4 = new int[][] { { 0, 1, 0, 0 } }; + Assertions.assertEquals(0, s.uniquePathsWithObstacles(input4)); + } + + static class Solution { + + public int uniquePathsWithObstacles(int[][] obstacleGrid) { + + // base case + if (obstacleGrid == null || obstacleGrid.length == 0) { return 0; } + int m = obstacleGrid.length, n = obstacleGrid[0].length; + // 起点、终点有障碍,注定无法到达 + if (obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1) { return 0; } + + // 状态定义 + int[][] dp = new int[m][n]; + + // 初始状态、边界状态 + dp[0][0] = 1; + for (int i = 1; i < m; i++) { dp[i][0] = (obstacleGrid[i][0] == 1) ? 0 : dp[i - 1][0]; } + for (int j = 1; j < n; j++) { dp[0][j] = (obstacleGrid[0][j] == 1) ? 0 : dp[0][j - 1]; } + + // 状态转移方程 + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + if (obstacleGrid[i][j] == 1) { + dp[i][j] = 0; + } else { + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + } + return dp[m - 1][n - 1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.java" new file mode 100644 index 0000000..2d4926a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\244\247\346\255\243\346\226\271\345\275\242.java" @@ -0,0 +1,80 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 221. 最大正方形 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最大正方形 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + char[][] input1 = new char[][] { + { '1', '0', '1', '0', '0' }, + { '1', '0', '1', '1', '1' }, + { '1', '1', '1', '1', '1' }, + { '1', '0', '0', '1', '0' } + }; + Assertions.assertEquals(4, s.maximalSquare(input1)); + + char[][] input2 = new char[][] { { '0', '1' }, { '1', '0' } }; + Assertions.assertEquals(1, s.maximalSquare(input2)); + + char[][] input3 = new char[][] { { '0' } }; + Assertions.assertEquals(0, s.maximalSquare(input3)); + + char[][] input4 = new char[][] { + { '1', '0', '1', '1', '0', '1' }, + { '1', '1', '1', '1', '1', '1' }, + { '0', '1', '1', '0', '1', '1' }, + { '1', '1', '1', '0', '1', '0' }, + { '0', '1', '1', '1', '1', '1' }, + { '1', '1', '0', '1', '1', '1' } + }; + Assertions.assertEquals(4, s.maximalSquare(input4)); + } + + static class Solution { + + public int maximalSquare(char[][] matrix) { + + // base case + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { return 0; } + + // 状态定义 + int m = matrix.length, n = matrix[0].length; + int[][] dp = new int[m][n]; + + // 状态转移方程 + int max = 0; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (i == 0 || j == 0) { + dp[i][j] = matrix[i][j] == '1' ? 1 : 0; + } else { + if (matrix[i][j] == '1') { + dp[i][j] = min( + dp[i - 1][j], + dp[i][j - 1], + dp[i - 1][j - 1] + ) + 1; + } + } + max = Math.max(dp[i][j], max); + } + } + return max * max; + } + + public int min(int a, int b, int c) { + return Math.min(Math.min(a, b), c); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" new file mode 100644 index 0000000..b20d79b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/matrix/\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.dp.matrix; + +import org.junit.jupiter.api.Assertions; + +/** + * 64. 最小路径和 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最小路径和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(7, s.minPathSum(new int[][] { { 1, 3, 1 }, { 1, 5, 1 }, { 4, 2, 1 } })); + Assertions.assertEquals(12, s.minPathSum(new int[][] { { 1, 2, 3 }, { 4, 5, 6 } })); + } + + static class Solution { + + public int minPathSum(int[][] grid) { + if (grid == null || grid.length == 0 || grid[0].length == 0) { return 0; } + int m = grid.length, n = grid[0].length; + int[][] dp = new int[m][n]; + dp[0][0] = grid[0][0]; + for (int i = 1; i < m; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } + for (int j = 1; j < n; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[m - 1][n - 1]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/package-info.java new file mode 100644 index 0000000..48674a2 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/package-info.java @@ -0,0 +1,7 @@ +/** + * 动态规划算法 + * + * @author Zhang Peng + * @since 2020-03-06 + */ +package io.github.dunwu.algorithm.dp; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.java" new file mode 100644 index 0000000..af5cc5f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272.java" @@ -0,0 +1,36 @@ +package io.github.dunwu.algorithm.dp.state; + +import org.junit.jupiter.api.Assertions; + +/** + * 121. 买卖股票的最佳时机 + * + * @author Zhang Peng + * @since 2020-07-05 + */ +public class 买卖股票的最佳时机 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.maxProfit(new int[] { 7, 1, 5, 3, 6, 4 })); + Assertions.assertEquals(0, s.maxProfit(new int[] { 7, 6, 4, 3, 1 })); + } + + static class Solution { + + public int maxProfit(int[] prices) { + int min = prices[0]; + int max = 0; + for (int i = 1; i < prices.length; i++) { + if (prices[i] <= min) { + min = prices[i]; + } else { + max = Math.max(max, prices[i] - min); + } + } + return max; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\2722.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\2722.java" new file mode 100644 index 0000000..98b90e7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\2722.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.dp.state; + +import org.junit.jupiter.api.Assertions; + +/** + * 122. 买卖股票的最佳时机 II + * + * @author Zhang Peng + * @since 2020-07-05 + */ +public class 买卖股票的最佳时机2 { + + public static void main(String[] args) { + int[] prices = { 7, 1, 5, 3, 6, 4 }; + int[] prices2 = { 1, 2, 3, 4, 5 }; + Assertions.assertEquals(7, maxProfit(prices)); + Assertions.assertEquals(4, maxProfit(prices2)); + } + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) return 0; + + int max = 0; + final int days = prices.length; + final int[][] dp = new int[days][2]; + + dp[0][0] = 0; + dp[0][1] = -prices[0]; + + for (int i = 1; i < days; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + max = Math.max(dp[i][0], dp[i][1]); + } + return max; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272III.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272III.java" new file mode 100644 index 0000000..916188a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272III.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.dp.state; + +import org.junit.jupiter.api.Assertions; + +/** + * @author Zhang Peng + * @see 123. 买卖股票的最佳时机 III + * @since 2020-07-05 + */ +public class 买卖股票的最佳时机III { + + public static void main(String[] args) { + int[] prices = { 3, 3, 5, 0, 0, 3, 1, 4 }; + int[] prices2 = { 1, 2, 3, 4, 5 }; + Assertions.assertEquals(6, maxProfit(prices)); + Assertions.assertEquals(4, maxProfit(prices2)); + } + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) return 0; + final int days = prices.length; + final int deal = 2; // 交易笔数 + + // 定义二维数组 + // 一维表示第 i 天 + // 二维表示交易笔数,最多 2 笔 + // 三维表示是否持有股票:0/1(持有) + int[][][] dp = new int[days][deal + 1][2]; + + // 第一天数据初始化 + for (int k = 0; k <= deal; k++) { + dp[0][k][0] = 0; + dp[0][k][1] = -prices[0]; + } + + for (int i = 1; i < days; i++) { // 扫描天数 + for (int k = deal; k >= 1; k--) { + dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]); + dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]); + } + } + + return dp[days - 1][deal][0]; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272IV.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272IV.java" new file mode 100644 index 0000000..f2bb072 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272IV.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.dp.state; + +import org.junit.jupiter.api.Assertions; + +/** + * @author Zhang Peng + * @see 188. 买卖股票的最佳时机 IV + * @since 2020-07-05 + */ +public class 买卖股票的最佳时机IV { + + public static void main(String[] args) { + int[] prices = { 2, 4, 1 }; + int[] prices2 = { 3, 2, 6, 5, 0, 3 }; + Assertions.assertEquals(2, maxProfit(2, prices)); + Assertions.assertEquals(7, maxProfit(2, prices2)); + } + + public static int maxProfit(final int k, int[] prices) { + if (prices == null || prices.length == 0) return 0; + final int days = prices.length; + if (k > days / 2) return maxProfit(prices); + + // 定义二维数组 + // 一维表示第 i 天 + // 二维表示交易笔数,最多 2 笔 + // 三维表示是否持有股票:0/1(持有) + int[][][] dp = new int[days][k + 1][2]; + + // 第一天数据初始化 + for (int j = 0; j <= k; j++) { + dp[0][j][0] = 0; + dp[0][j][1] = -prices[0]; + } + + for (int i = 1; i < days; i++) { // 扫描天数 + for (int j = k; j >= 1; j--) { + dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]); + dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]); + } + } + + return dp[days - 1][k][0]; + } + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) return 0; + + int max = 0; + final int days = prices.length; + final int[][] dp = new int[days][2]; + + dp[0][0] = 0; + dp[0][1] = -prices[0]; + + for (int i = 1; i < days; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + max = Math.max(dp[i][0], dp[i][1]); + } + return max; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.java" new file mode 100644 index 0000000..134b337 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\272\345\220\253\346\211\213\347\273\255\350\264\271.java" @@ -0,0 +1,36 @@ +package io.github.dunwu.algorithm.dp.state; + +import org.junit.jupiter.api.Assertions; + +/** + * @author Zhang Peng + * @see 714. + * 买卖股票的最佳时机含手续费 + * @since 2020-07-05 + */ +public class 买卖股票的最佳时机含手续费 { + + public static void main(String[] args) { + int[] prices = { 1, 3, 2, 8, 4, 9 }; + Assertions.assertEquals(8, maxProfit(prices, 2)); + } + + public static int maxProfit(int[] prices, int fee) { + if (prices == null || prices.length == 0) return 0; + + int max = 0; + final int days = prices.length; + final int[][] dp = new int[days][2]; + + dp[0][0] = 0; + dp[0][1] = -prices[0]; + + for (int i = 1; i < days; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + max = Math.max(dp[i][0], dp[i][1]); + } + return max; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.java" new file mode 100644 index 0000000..9836d29 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/state/\346\234\200\344\275\263\344\271\260\345\215\226\350\202\241\347\245\250\346\227\266\346\234\272\345\220\253\345\206\267\345\206\273\346\234\237.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.dp.state; + +import org.junit.jupiter.api.Assertions; + +/** + * @author Zhang Peng + * @see 309. 最佳买卖股票时机含冷冻期 + * @since 2020-07-05 + */ +public class 最佳买卖股票时机含冷冻期 { + + public static void main(String[] args) { + int[] prices = { 1, 2, 3, 0, 2 }; + Assertions.assertEquals(3, maxProfit(prices)); + } + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) return 0; + + int max = 0; + final int days = prices.length; + final int[][][] dp = new int[days][2][2]; + + dp[0][0][0] = 0; + dp[0][0][1] = 0; + dp[0][1][0] = -prices[0]; + + for (int i = 1; i < days; i++) { + dp[i][0][0] = Math.max(dp[i - 1][0][0], dp[i - 1][0][1]); + dp[i][0][1] = dp[i - 1][1][0] + prices[i]; + dp[i][1][0] = Math.max(dp[i - 1][0][0] - prices[i], dp[i - 1][1][0]); + + int temp1 = Math.max(dp[i][0][0], dp[i][0][1]); + int temp2 = Math.max(dp[i][1][0], temp1); + max = Math.max(max, temp2); + } + return max; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\345\210\240\351\231\244\346\223\215\344\275\234.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\345\210\240\351\231\244\346\223\215\344\275\234.java" new file mode 100644 index 0000000..a3df26e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\345\210\240\351\231\244\346\223\215\344\275\234.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 583. 两个字符串的删除操作 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 两个字符串的删除操作 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.minDistance("sea", "eat")); + } + + static class Solution { + + public int minDistance(String word1, String word2) { + int lcs = longestCommonSubsequence(word1, word2); + return word1.length() + word2.length() - lcs - lcs; + } + + public int longestCommonSubsequence(String text1, String text2) { + int[][] memo = new int[text1.length()][text2.length()]; + for (int i = 0; i < text1.length(); i++) { + Arrays.fill(memo[i], -1); + } + return dp(memo, text1, 0, text2, 0); + } + + public int dp(int[][] memo, String text1, int i, String text2, int j) { + if (i < 0 || j < 0 || i >= text1.length() || j >= text2.length()) { return 0; } + if (memo[i][j] != -1) { return memo[i][j]; } + + if (text1.charAt(i) == text2.charAt(j)) { + memo[i][j] = 1 + dp(memo, text1, i + 1, text2, j + 1); + } else { + memo[i][j] = max( + dp(memo, text1, i + 1, text2, j), + dp(memo, text1, i, text2, j + 1), + dp(memo, text1, i + 1, text2, j + 1) + ); + } + return memo[i][j]; + } + + public int max(int a, int b, int c) { + return Math.max(a, Math.max(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\260\217ASCII\345\210\240\351\231\244\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\260\217ASCII\345\210\240\351\231\244\345\222\214.java" new file mode 100644 index 0000000..18a5317 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\260\217ASCII\345\210\240\351\231\244\345\222\214.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +/** + * 712. 两个字符串的最小ASCII删除和 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 两个字符串的最小ASCII删除和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(231, s.minimumDeleteSum("sea", "eat")); + Assertions.assertEquals(403, s.minimumDeleteSum("delete", "leet")); + } + + static class Solution { + + public int minimumDeleteSum(String s1, String s2) { + int m = s1.length(), n = s2.length(); + int[][] dp = new int[m + 1][n + 1]; + for (int i = 1; i <= m; i++) { + dp[i][0] = dp[i - 1][0] + s1.codePointAt(i - 1); + } + for (int j = 1; j <= n; j++) { + dp[0][j] = dp[0][j - 1] + s2.codePointAt(j - 1); + } + for (int i = 1; i <= m; i++) { + int code1 = s1.codePointAt(i - 1); + for (int j = 1; j <= n; j++) { + int code2 = s2.codePointAt(j - 1); + if (code1 == code2) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(dp[i - 1][j] + code1, dp[i][j - 1] + code2); + } + } + } + return dp[m][n]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\345\215\225\350\257\215\346\213\206\345\210\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\345\215\225\350\257\215\346\213\206\345\210\206.java" new file mode 100644 index 0000000..97e11ea --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\345\215\225\350\257\215\346\213\206\345\210\206.java" @@ -0,0 +1,112 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 139. 单词拆分 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 单词拆分 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.wordBreak("leetcode", Arrays.asList("leet", "code"))); + Assertions.assertTrue(s.wordBreak("applepenapple", Arrays.asList("apple", "pen"))); + Assertions.assertFalse(s.wordBreak("catsandog", Arrays.asList("cats", "dog", "sand", "and", "cat"))); + + Solution2 s2 = new Solution2(); + Assertions.assertTrue(s2.wordBreak("leetcode", Arrays.asList("leet", "code"))); + Assertions.assertTrue(s2.wordBreak("applepenapple", Arrays.asList("apple", "pen"))); + Assertions.assertFalse(s2.wordBreak("catsandog", Arrays.asList("cats", "dog", "sand", "and", "cat"))); + } + + // 回溯解决方案 + static class Solution { + + // 记录是否找到一个合法的答案 + boolean found = false; + // 记录回溯算法的路径 + private LinkedList path; + + public boolean wordBreak(String s, List wordDict) { + found = false; + path = new LinkedList<>(); + backtrack(wordDict, s, 0); + return found; + } + + public void backtrack(List wordDict, String target, int start) { + + // 找到一个合法答案 + if (start == target.length()) { found = true; } + // 如果已经找到答案,就不要再递归搜索了 + if (found) { return; } + + // 回溯算法框架 + for (String word : wordDict) { + + int len = word.length(); + + // 无效情况,剪枝 + if (start + len > target.length()) { return; } + if (!target.substring(start, start + len).equals(word)) { continue; } + + // 【选择】 + path.add(word); + // 【回溯】 + backtrack(wordDict, target, start + len); + // 【取消选择】 + path.remove(path.size() - 1); + } + } + + } + + static class Solution2 { + + // 备忘录,-1 代表未计算,0 代表无法凑出,1 代表可以凑出 + private int[] memo; + // 用哈希集合方便快速判断是否存在 + HashSet wordDict; + + public boolean wordBreak(String s, List wordDict) { + this.wordDict = new HashSet<>(wordDict); + this.memo = new int[s.length()]; + Arrays.fill(memo, -1); + return dp(s, 0); + } + + public boolean dp(String s, int index) { + // base case + if (index == s.length()) { return true; } + // 避免冗余 + if (memo[index] != -1) { return memo[index] == 0 ? false : true; } + + // 遍历 s[i..] 的所有前缀 + for (int len = 1; index + len <= s.length(); len++) { + // 看看哪些前缀存在 wordDict 中 + String prefix = s.substring(index, index + len); + if (wordDict.contains(prefix)) { + // 找到一个单词匹配 s[i..i+len) + // 只要 s[i+len..] 可以被拼出,s[i..] 就能被拼出 + if (dp(s, index + len)) { + memo[index] = 1; + return true; + } + } + } + // s[i..] 无法被拼出 + memo[index] = 0; + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..4104ba8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\346\234\200\351\225\277\345\233\236\346\226\207\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +/** + * 516. 最长回文子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长回文子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.longestPalindromeSubseq("bbbab")); + Assertions.assertEquals(2, s.longestPalindromeSubseq("v")); + } + + static class Solution { + + public int longestPalindromeSubseq(String s) { + int n = s.length(); + int[][] dp = new int[n][n]; + for (int i = n - 1; i >= 0; i--) { + dp[i][i] = 1; + for (int j = i + 1; j < n; j++) { + if (s.charAt(i) == s.charAt(j)) { + dp[i][j] = dp[i + 1][j - 1] + 2; + } else { + dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]); + } + } + } + return dp[0][n - 1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\347\274\226\350\276\221\350\267\235\347\246\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\347\274\226\350\276\221\350\267\235\347\246\273.java" new file mode 100644 index 0000000..2dc83a4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/str/\347\274\226\350\276\221\350\267\235\347\246\273.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.dp.str; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 72. 编辑距离 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 编辑距离 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.minDistance("horse", "ros")); + Assertions.assertEquals(5, s.minDistance("intention", "execution")); + } + + static class Solution { + + int[][] memo; + + public int minDistance(String word1, String word2) { + memo = new int[word1.length()][word2.length()]; + for (int i = 0; i < word1.length(); i++) { + Arrays.fill(memo[i], Integer.MAX_VALUE); + } + return dp(word1, 0, word2, 0); + } + + public int dp(String word1, int i, String word2, int j) { + if (i >= word1.length()) { return word2.length() - j; } + if (j >= word2.length()) { return word1.length() - i; } + if (memo[i][j] != Integer.MAX_VALUE) { + return memo[i][j]; + } + if (word1.charAt(i) == word2.charAt(j)) { + memo[i][j] = dp(word1, i + 1, word2, j + 1); + } else { + memo[i][j] = min( + dp(word1, i + 1, word2, j), + dp(word1, i, word2, j + 1), + dp(word1, i + 1, word2, j + 1) + ) + 1; + } + return memo[i][j]; + } + + public int min(int a, int b, int c) { + return Math.min(a, Math.min(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\344\270\215\347\233\270\344\272\244\347\232\204\347\272\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\344\270\215\347\233\270\344\272\244\347\232\204\347\272\277.java" new file mode 100644 index 0000000..290ae9d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\344\270\215\347\233\270\344\272\244\347\232\204\347\272\277.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 300. 最长递增子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 不相交的线 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.maxUncrossedLines(new int[] { 1, 4, 2 }, new int[] { 1, 2, 4 })); + } + + static class Solution { + + int[][] memo; + + public int maxUncrossedLines(int[] nums1, int[] nums2) { + memo = new int[nums1.length + 1][nums2.length + 1]; + for (int i = 0; i <= nums1.length; i++) { + Arrays.fill(memo[i], -1); + } + return dp(nums1, 0, nums2, 0); + } + + public int dp(int[] nums1, int i, int[] nums2, int j) { + if (i < 0 || i >= nums1.length || j < 0 || j >= nums2.length) { return 0; } + if (memo[i][j] != -1) { return memo[i][j]; } + if (nums1[i] == nums2[j]) { + memo[i][j] = dp(nums1, i + 1, nums2, j + 1) + 1; + } else { + memo[i][j] = max( + dp(nums1, i, nums2, j + 1), + dp(nums1, i + 1, nums2, j), + dp(nums1, i + 1, nums2, j + 1) + ); + } + return memo[i][j]; + } + + public int max(int a, int b, int c) { + return Math.max(a, Math.max(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..ad63c86 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\345\210\244\346\226\255\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +/** + * 392. 判断子序列 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 判断子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isSubsequence("abc", "ahbgdc")); + Assertions.assertFalse(s.isSubsequence("axc", "ahbgdc")); + Assertions.assertTrue(s.isSubsequence("", "ahbgdc")); + Assertions.assertFalse(s.isSubsequence("aaaaaa", "bbaaaa")); + } + + static class Solution { + + public boolean isSubsequence(String s, String t) { + int m = s.length(), n = t.length(); + + // dp[i][j] 表示 s 的前 i 个字符是否是 t 的前 j 个字符的子序列 + boolean[][] dp = new boolean[m + 1][n + 1]; + + // 初始化:空字符串是任何字符串的子序列 + for (int j = 0; j <= n; j++) { + dp[0][j] = true; + } + + // 动态规划填表 + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + if (s.charAt(i - 1) == t.charAt(j - 1)) { + // 字符匹配,取决于前一个状态 + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 字符不匹配,只能尝试在 t 中继续寻找 + dp[i][j] = dp[i][j - 1]; + } + } + } + + return dp[m][n]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.java" new file mode 100644 index 0000000..22b2183 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\345\244\247\345\255\220\345\272\217\345\222\214.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +/** + * 53. 最大子序和 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 最大子序和 { + + public static void main(String[] args) { + int[] nums = { -2, 1, -3, 4, -1, 2, 1, -5, 4 }; + Assertions.assertEquals(6, maxSubArray(nums)); + Assertions.assertEquals(-1, maxSubArray(new int[] { -1 })); + } + + public static int maxSubArray(int[] nums) { + if (nums == null || nums.length == 0) return 0; + + int[] dp = new int[nums.length + 1]; + dp[0] = nums[0]; + int max = dp[0]; + for (int i = 1; i < nums.length; i++) { + dp[i] = dp[i - 1] >= 0 ? dp[i - 1] + nums[i] : nums[i]; + } + + for (int i = 0; i < nums.length; i++) { + if (max < dp[i]) { + max = dp[i]; + } + } + return max; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..d7ff0fe --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,35 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +/** + * 300. 最长上升子序列 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 最长上升子序列 { + + public static void main(String[] args) { + int[] nums = { 10, 9, 2, 5, 3, 7, 101, 18 }; + Assertions.assertEquals(4, lengthOfLIS(nums)); + Assertions.assertEquals(1, lengthOfLIS(new int[] { 0 })); + } + + public static int lengthOfLIS(int[] nums) { + if (nums == null || nums.length == 0) return 0; + int max = 1; + final int[] dp = new int[nums.length + 1]; + for (int i = 0; i < nums.length; i++) dp[i] = 1; + for (int i = 1; i < nums.length; i++) { + for (int j = 0; j < i; j++) { + if (nums[j] < nums[i]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + max = Math.max(max, dp[i]); + } + return max; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..cf7c08e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,56 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 1143. 最长公共子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长公共子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.longestCommonSubsequence("abcde", "ace")); + Assertions.assertEquals(3, s.longestCommonSubsequence("abc", "abc")); + Assertions.assertEquals(0, s.longestCommonSubsequence("abc", "def")); + } + + static class Solution { + + private int[][] memo; + + public int longestCommonSubsequence(String text1, String text2) { + int m = text1.length(), n = text2.length(); + memo = new int[m + 1][n + 1]; + for (int i = 0; i <= m; i++) { + Arrays.fill(memo[i], -1); + } + return dp(text1, 0, text2, 0); + } + + public int dp(String text1, int i, String text2, int j) { + if (i < 0 || i >= text1.length() || j < 0 || j >= text2.length()) { return 0; } + if (memo[i][j] != -1) { return memo[i][j]; } + if (text1.charAt(i) == text2.charAt(j)) { + memo[i][j] = dp(text1, i + 1, text2, j + 1) + 1; + } else { + memo[i][j] = max( + dp(text1, i + 1, text2, j), + dp(text1, i, text2, j + 1), + dp(text1, i + 1, text2, j + 1) + ); + } + return memo[i][j]; + } + + public int max(int a, int b, int c) { + return Math.max(a, Math.max(b, c)); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\256\232\345\267\256\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\256\232\345\267\256\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..d7ae1d4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\345\256\232\345\267\256\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 1218. 最长定差子序列 + * + * @author Zhang Peng + * @date 2025-11-14 + */ +public class 最长定差子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.longestSubsequence(new int[] { 1, 2, 3, 4 }, 1)); + Assertions.assertEquals(1, s.longestSubsequence(new int[] { 1, 3, 5, 7 }, 1)); + Assertions.assertEquals(4, s.longestSubsequence(new int[] { 1, 5, 7, 8, 5, 3, 4, 2, 1 }, -2)); + Assertions.assertEquals(2, s.longestSubsequence(new int[] { 3, 4, -3, -2, -4 }, -5)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(4, s2.longestSubsequence(new int[] { 1, 2, 3, 4 }, 1)); + Assertions.assertEquals(1, s2.longestSubsequence(new int[] { 1, 3, 5, 7 }, 1)); + Assertions.assertEquals(4, s2.longestSubsequence(new int[] { 1, 5, 7, 8, 5, 3, 4, 2, 1 }, -2)); + Assertions.assertEquals(2, s2.longestSubsequence(new int[] { 3, 4, -3, -2, -4 }, -5)); + } + + static class Solution { + + public int longestSubsequence(int[] arr, int diff) { + int n = arr.length; + Map map = new HashMap<>(); + int[][] dp = new int[n][2]; + dp[0][1] = 1; + map.put(arr[0], 0); + for (int i = 1; i < n; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); + dp[i][1] = 1; + int prev = arr[i] - diff; + if (map.containsKey(prev)) dp[i][1] = Math.max(dp[i][1], dp[map.get(prev)][1] + 1); + map.put(arr[i], i); + } + return Math.max(dp[n - 1][0], dp[n - 1][1]); + } + + } + + static class Solution2 { + + public int longestSubsequence(int[] arr, int diff) { + int res = 1; + Map map = new HashMap<>(); + for (int val : arr) { + map.put(val, map.getOrDefault(val - diff, 0) + 1); + res = Math.max(res, map.get(val)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\346\225\260\345\257\271\351\223\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\346\225\260\345\257\271\351\223\276.java" new file mode 100644 index 0000000..ae2ef0d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\346\225\260\345\257\271\351\223\276.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * 646. 最长数对链 + * + * @author Zhang Peng + * @date 2025-11-14 + */ +public class 最长数对链 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input1 = new int[][] { { 1, 2 }, { 2, 3 }, { 3, 4 } }; + Assertions.assertEquals(2, s.findLongestChain(input1)); + + int[][] input2 = new int[][] { { 1, 2 }, { 7, 8 }, { 4, 5 } }; + Assertions.assertEquals(3, s.findLongestChain(input2)); + } + + static class Solution { + + public int findLongestChain(int[][] pairs) { + Arrays.sort(pairs, Comparator.comparingInt(pair -> pair[0])); + int n = pairs.length; + int[] dp = new int[n]; + for (int i = 0; i < n; i++) { + dp[i] = 1; + for (int j = 0; j < i; j++) { + if (pairs[i][0] > pairs[j][1]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + } + return dp[n - 1]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\347\255\211\345\267\256\346\225\260\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\347\255\211\345\267\256\346\225\260\345\210\227.java" new file mode 100644 index 0000000..e0b1b49 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\347\255\211\345\267\256\346\225\260\345\210\227.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 1027. 最长等差数列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长等差数列 { + + public static void main(String[] args) { + Solution s = new Solution(); + // Assertions.assertEquals(4, s.longestArithSeqLength(new int[] { 3, 6, 9, 12 })); + // Assertions.assertEquals(3, s.longestArithSeqLength(new int[] { 9, 4, 7, 2, 10 })); + Assertions.assertEquals(4, s.longestArithSeqLength(new int[] { 20, 1, 15, 3, 10, 5, 8 })); + } + + static class Solution { + + public int longestArithSeqLength(int[] nums) { + int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE; + for (int num : nums) { + min = Math.min(min, num); + max = Math.max(max, num); + } + + int res = 1; + int maxDiff = max - min; + for (int diff = -maxDiff; diff <= maxDiff; diff++) { + res = Math.max(longestSubsequence(nums, diff), res); + } + return res; + } + + public int longestSubsequence(int[] arr, int diff) { + int res = 1; + Map map = new HashMap<>(); + for (int val : arr) { + map.put(val, map.getOrDefault(val - diff, 0) + 1); + res = Math.max(res, map.get(val)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.java" new file mode 100644 index 0000000..ad8cb65 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/subseq/\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.dp.subseq; + +import org.junit.jupiter.api.Assertions; + +/** + * 300. 最长递增子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长递增子序列 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.lengthOfLIS(new int[] { 10, 9, 2, 5, 3, 7, 101, 18 })); + Assertions.assertEquals(4, s.lengthOfLIS(new int[] { 0, 1, 0, 3, 2, 3 })); + Assertions.assertEquals(1, s.lengthOfLIS(new int[] { 7, 7, 7, 7, 7, 7, 7 })); + } + + static class Solution { + + public int lengthOfLIS(int[] nums) { + int n = nums.length; + int[] dp = new int[n]; + int max = 1; + for (int i = 0; i < n; i++) { + dp[i] = 1; + for (int j = 0; j < i; j++) { + // 枚举区间 [0,i) 的所有数 nums[j],如果满足 nums[j]300. 最长递增子序列 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最长递增子序列的个数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.findNumberOfLIS(new int[] { 1, 3, 5, 4, 7 })); + Assertions.assertEquals(5, s.findNumberOfLIS(new int[] { 2, 2, 2, 2, 2 })); + Assertions.assertEquals(3, s.findNumberOfLIS(new int[] { 1, 2, 4, 3, 5, 4, 7, 2 })); + } + + static class Solution { + + public int findNumberOfLIS(int[] nums) { + + int n = nums.length; + int[] dp = new int[n]; + int[] cnt = new int[n]; + int max = 1; + for (int i = 0; i < n; i++) { + dp[i] = cnt[i] = 1; + for (int j = 0; j < i; j++) { + if (nums[j] < nums[i]) { + if (dp[i] < dp[j] + 1) { + dp[i] = dp[j] + 1; + cnt[i] = cnt[j]; + } else if (dp[i] == dp[j] + 1) { + cnt[i] += cnt[j]; + } + } + } + max = Math.max(max, dp[i]); + } + int res = 0; + for (int i = 0; i < n; i++) { + if (dp[i] == max) res += cnt[i]; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\346\250\241\346\235\277.java" new file mode 100644 index 0000000..654e676 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\346\250\241\346\235\277.java" @@ -0,0 +1,25 @@ +package io.github.dunwu.algorithm.dp.template; + +/** + * 动态规划模板 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 动态规划模板 { + // 自顶向下递归的动态规划 + // def dp(状态1, 状态2, ...): + // for 选择 in 所有可能的选择: + // # 此时的状态已经因为做了选择而改变 + // result = 求最值(result, dp(状态1, 状态2, ...)) + // return result + + // 自底向上迭代的动态规划 + // 初始化 base case + // dp[0][0][...] = base case + // # 进行状态转移 + // for 状态1 in 状态1的所有取值: + // for 状态2 in 状态2的所有取值: + // for ... + // dp[状态1][状态2][...] = 求最值(选择1,选择2...) +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\350\247\243\350\203\214\345\214\205\351\227\256\351\242\230\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\350\247\243\350\203\214\345\214\205\351\227\256\351\242\230\346\250\241\346\235\277.java" new file mode 100644 index 0000000..ded4738 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/template/\345\212\250\346\200\201\350\247\204\345\210\222\350\247\243\350\203\214\345\214\205\351\227\256\351\242\230\346\250\241\346\235\277.java" @@ -0,0 +1,22 @@ +package io.github.dunwu.algorithm.dp.template; + +/** + * 动态规划解背包问题模板 + * + * @author Zhang Peng + * @date 2025-12-17 + */ +public class 动态规划解背包问题模板 { + + // int[][] dp[N+1][W+1] + // dp[0][..] = 0 + // dp[..][0] = 0 + // + // for i in [1..N]: + // for w in [1..W]: + // dp[i][w] = max( + // 把物品 i 装进背包, + // 不把物品 i 装进背包 + // ) + // return dp[N][W] +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.java" new file mode 100644 index 0000000..3f7dff2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\344\271\230\347\247\257\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +/** + * @author Zhang Peng + * @since 2020-07-05 + */ +public class 乘积最大子数组 { + + public static void main(String[] args) { + int[] nums = { 2, 3, -2, 4 }; + int[] nums2 = { -2, 0, -1 }; + // Assertions.assertEquals(6, maxProduct(nums)); + Assertions.assertEquals(6, maxProduct2(nums)); + Assertions.assertEquals(0, maxProduct2(nums2)); + } + + public static int maxProduct(int[] nums) { + return backtrack(nums, 0, 0, 1, 0); + } + + // 递归 + 回溯 暴力破解 + // 时间复杂度 O(2^N) + public static int backtrack(int[] nums, int begin, int end, int res, int max) { + if (end >= nums.length || begin > end) return max; + res *= nums[end]; + if (res > max) { + return backtrack(nums, begin, end + 1, res, res); + } else { + return backtrack(nums, end + 1, end + 1, 1, max); + } + } + + public static int maxProduct2(int[] nums) { + int min = nums[0]; + int max = nums[0]; + int res = nums[0]; + for (int i = 1; i < nums.length; i++) { + int currMax = Math.max(Math.max(nums[i] * max, nums[i] * min), nums[i]); + int currMin = Math.min(Math.min(nums[i] * max, nums[i] * min), nums[i]); + res = Math.max(currMax, res); + max = currMax; + min = currMin; + } + return res; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.java" new file mode 100644 index 0000000..f478807 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +/** + * 416. 分割等和子集 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 分割等和子集 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canPartition(new int[] { 1, 5, 11, 5 })); + Assertions.assertFalse(s.canPartition(new int[] { 1, 2, 3, 5 })); + } + + static class Solution { + + public boolean canPartition(int[] wights) { + + int sum = 0; + for (int weight : wights) { + sum += weight; + } + + // 和为奇数时,不可能划分成两个和相等的集合 + if (sum % 2 != 0) return false; + + // 初始化为背包问题 + int W = sum / 2; + int N = wights.length; + + // base case + boolean[][] dp = new boolean[N + 1][W + 1]; + for (int i = 0; i <= N; i++) + dp[i][0] = true; + + for (int i = 1; i <= N; i++) { + for (int w = 1; w <= W; w++) { + if (w - wights[i - 1] < 0) { + dp[i][w] = dp[i - 1][w]; + } else { + dp[i][w] = dp[i - 1][w] + || dp[i - 1][w - wights[i - 1]]; + } + } + } + return dp[N][W]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.java" new file mode 100644 index 0000000..e7af1a6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +/** + * 53. 最大子数组和 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 最大子数组和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.maxSubArray(new int[] { -2, 1, -3, 4, -1, 2, 1, -5, 4 })); + } + + static class Solution { + + public int maxSubArray(int[] nums) { + int n = nums.length; + if (n == 0) return 0; + int[] dp = new int[n]; + // base case + // 第一个元素前面没有子数组 + dp[0] = nums[0]; + // 状态转移方程 + int res = Integer.MIN_VALUE; + for (int i = 1; i < n; i++) { + dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]); + res = Math.max(res, dp[i]); + System.out.printf("nums[%d] = %d, dp[%d] = %d\n", i, nums[i], i, dp[i]); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\222.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\222.java" new file mode 100644 index 0000000..4ace68d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\222.java" @@ -0,0 +1,54 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * 118. 杨辉三角 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 杨辉三角 { + + public static void main(String[] args) { + Solution s = new Solution(); + List> expect = new ArrayList<>(); + expect.add(Arrays.asList(1)); + expect.add(Arrays.asList(1, 1)); + expect.add(Arrays.asList(1, 2, 1)); + expect.add(Arrays.asList(1, 3, 3, 1)); + expect.add(Arrays.asList(1, 4, 6, 4, 1)); + List> lists = s.generate(5); + Assertions.assertArrayEquals(expect.toArray(), lists.toArray()); + } + + static class Solution { + + public List> generate(int row) { + int[][] matrix = new int[row][row]; + matrix[0][0] = 1; + List> res = new ArrayList<>(); + res.add(Collections.singletonList(1)); + for (int i = 1; i < row; i++) { + List list = new ArrayList<>(); + for (int j = 0; j <= i; j++) { + if (j == 0) { + matrix[i][j] = matrix[i - 1][j]; + } else { + matrix[i][j] = matrix[i - 1][j] + matrix[i - 1][j - 1]; + } + list.add(matrix[i][j]); + } + res.add(list); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\2222.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\2222.java" new file mode 100644 index 0000000..a6a3faa --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\346\235\250\350\276\211\344\270\211\350\247\2222.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 119. 杨辉三角 II + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 杨辉三角2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 3, 1 }, s.getRow(3).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, s.getRow(0).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 1 }, s.getRow(1).toArray()); + } + + static class Solution { + + public List getRow(int rowIndex) { + int row = rowIndex + 1; + int[][] matrix = new int[row][row]; + matrix[0][0] = 1; + List> res = new ArrayList<>(); + res.add(Collections.singletonList(1)); + for (int i = 1; i < row; i++) { + List list = new ArrayList<>(); + for (int j = 0; j <= i; j++) { + if (j == 0) { + matrix[i][j] = matrix[i - 1][j]; + } else { + matrix[i][j] = matrix[i - 1][j] + matrix[i - 1][j - 1]; + } + list.add(matrix[i][j]); + } + res.add(list); + } + return res.get(rowIndex); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\351\233\266\351\222\261\345\205\221\346\215\2422.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\351\233\266\351\222\261\345\205\221\346\215\2422.java" new file mode 100644 index 0000000..c0b0b76 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/dp/\351\233\266\351\222\261\345\205\221\346\215\2422.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.dp; + +import org.junit.jupiter.api.Assertions; + +/** + * 518. 零钱兑换 II + * + * @author Zhang Peng + * @date 2025-11-11 + */ +public class 零钱兑换2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.change(5, new int[] { 1, 2, 5 })); + Assertions.assertEquals(0, s.change(3, new int[] { 2 })); + Assertions.assertEquals(1, s.change(10, new int[] { 10 })); + } + + static class Solution { + + public int change(int amount, int[] coins) { + int n = coins.length; + int[][] dp = new int[n + 1][amount + 1]; + // base case + for (int i = 0; i <= n; i++) + dp[i][0] = 1; + + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= amount; j++) + if (j - coins[i - 1] >= 0) { + dp[i][j] = dp[i - 1][j] + + dp[i][j - coins[i - 1]]; + } else { dp[i][j] = dp[i - 1][j]; } + } + return dp[n][amount]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Edge.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Edge.java new file mode 100644 index 0000000..c4ee513 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Edge.java @@ -0,0 +1,16 @@ +package io.github.dunwu.algorithm.graph; + +/** + * 存储相邻节点及边的权重 + */ +public class Edge { + + public int to; + public int weight; + + public Edge(int to, int weight) { + this.to = to; + this.weight = weight; + } + +} \ No newline at end of file diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Graph.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Graph.java new file mode 100644 index 0000000..eaf8c0c --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Graph.java @@ -0,0 +1,25 @@ +package io.github.dunwu.algorithm.graph; + +import java.util.List; + +public interface Graph { + + // 添加一条边(带权重) + void addEdge(int from, int to, int weight); + + // 删除一条边 + void removeEdge(int from, int to); + + // 判断两个节点是否相邻 + boolean hasEdge(int from, int to); + + // 返回一条边的权重 + int weight(int from, int to); + + // 返回某个节点的所有邻居节点和对应权重 + List neighbors(int v); + + // 返回节点总数 + int size(); + +} \ No newline at end of file diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/State.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/State.java new file mode 100644 index 0000000..3b78a8f --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/State.java @@ -0,0 +1,17 @@ +package io.github.dunwu.algorithm.graph; + +// 图结构的 BFS 遍历,从节点 s 开始进行 BFS,且记录遍历步数(从起点 s 到当前节点的边的条数) +// 每个节点自行维护 State 类,记录从 s 走来的遍历步数 +public class State { + + // 当前节点 ID + public int node; + // 从起点 s 到当前节点的遍历步数 + public int step; + + public State(int node, int step) { + this.node = node; + this.step = step; + } + +} \ No newline at end of file diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Vertex.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Vertex.java new file mode 100644 index 0000000..46dec8a --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/Vertex.java @@ -0,0 +1,11 @@ +package io.github.dunwu.algorithm.graph; + +/** + * 图节点 + */ +public class Vertex { + + public int id; + public Vertex[] neighbors; + +} \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\210\244\346\226\255\344\272\214\345\210\206\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\210\244\346\226\255\344\272\214\345\210\206\345\233\276.java" new file mode 100644 index 0000000..a78231e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\210\244\346\226\255\344\272\214\345\210\206\345\233\276.java" @@ -0,0 +1,139 @@ +package io.github.dunwu.algorithm.graph.bipartite; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 785. 判断二分图 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 判断二分图 { + + public static void main(String[] args) { + + int[][] input = new int[][] { { 1, 2, 3 }, { 0, 2 }, { 0, 1, 3 }, { 0, 2 } }; + int[][] input2 = new int[][] { { 1, 3 }, { 0, 2 }, { 1, 3 }, { 0, 2 } }; + + Solution s = new Solution(); + Assertions.assertFalse(s.isBipartite(input)); + Assertions.assertFalse(s.isBipartite(input2)); + + Solution2 s2 = new Solution2(); + Assertions.assertFalse(s2.isBipartite(input)); + Assertions.assertFalse(s2.isBipartite(input2)); + } + + // 二分图算法(DFS 版本) + static class Solution { + + // 记录图是否符合二分图性质 + private boolean ok = true; + // 记录图中节点的颜色,false 和 true 代表两种不同颜色 + private boolean[] color; + // 记录图中节点是否被访问过 + private boolean[] visited; + + // 主函数,输入邻接表,判断是否是二分图 + public boolean isBipartite(int[][] graph) { + int n = graph.length; + color = new boolean[n]; + visited = new boolean[n]; + // 因为图不一定是联通的,可能存在多个子图 + // 所以要把每个节点都作为起点进行一次遍历 + // 如果发现任何一个子图不是二分图,整幅图都不算二分图 + for (int v = 0; v < n; v++) { + if (!visited[v]) { + dfs(graph, v); + } + } + return ok; + } + + // DFS 遍历框架 + private void dfs(int[][] graph, int v) { + // 如果已经确定不是二分图了,就不用浪费时间再递归遍历了 + if (!ok) return; + + visited[v] = true; + for (int w : graph[v]) { + if (!visited[w]) { + // 相邻节点 w 没有被访问过 + // 那么应该给节点 w 涂上和节点 v 不同的颜色 + color[w] = !color[v]; + // 继续遍历 w + dfs(graph, w); + } else { + // 相邻节点 w 已经被访问过 + // 根据 v 和 w 的颜色判断是否是二分图 + if (color[w] == color[v]) { + // 若相同,则此图不是二分图 + ok = false; + } + } + } + } + + } + + // 二分图算法(BFS 版本) + static class Solution2 { + + // 记录图是否符合二分图性质 + private boolean ok = true; + // 记录图中节点的颜色,false 和 true 代表两种不同颜色 + private boolean[] color; + // 记录图中节点是否被访问过 + private boolean[] visited; + + public boolean isBipartite(int[][] graph) { + int n = graph.length; + color = new boolean[n]; + visited = new boolean[n]; + + for (int v = 0; v < n; v++) { + if (!visited[v]) { + // 改为使用 BFS 函数 + bfs(graph, v); + } + } + + return ok; + } + + // 从 start 节点开始进行 BFS 遍历 + private void bfs(int[][] graph, int start) { + Queue q = new LinkedList<>(); + visited[start] = true; + q.offer(start); + + while (!q.isEmpty() && ok) { + int v = q.poll(); + // 从节点 v 向所有相邻节点扩散 + for (int w : graph[v]) { + if (!visited[w]) { + // 相邻节点 w 没有被访问过 + // 那么应该给节点 w 涂上和节点 v 不同的颜色 + color[w] = !color[v]; + // 标记 w 节点,并放入队列 + visited[w] = true; + q.offer(w); + } else { + // 相邻节点 w 已经被访问过 + // 根据 v 和 w 的颜色判断是否是二分图 + if (color[w] == color[v]) { + // 若相同,则此图不是二分图 + ok = false; + return; + } + } + } + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\217\257\350\203\275\347\232\204\344\272\214\345\210\206\346\263\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\217\257\350\203\275\347\232\204\344\272\214\345\210\206\346\263\225.java" new file mode 100644 index 0000000..1dc1d9b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/bipartite/\345\217\257\350\203\275\347\232\204\344\272\214\345\210\206\346\263\225.java" @@ -0,0 +1,86 @@ +package io.github.dunwu.algorithm.graph.bipartite; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 886. 可能的二分法 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 可能的二分法 { + + public static void main(String[] args) { + + int[][] input = new int[][] { { 1, 2 }, { 1, 3 }, { 2, 4 } }; + int[][] input2 = new int[][] { { 1, 2 }, { 1, 3 }, { 2, 3 } }; + int[][] input3 = new int[][] { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 1, 5 } }; + + Solution s = new Solution(); + Assertions.assertTrue(s.possibleBipartition(4, input)); + Assertions.assertFalse(s.possibleBipartition(3, input2)); + Assertions.assertFalse(s.possibleBipartition(5, input3)); + } + + static class Solution { + + private boolean ok = true; + private boolean[] color; + private boolean[] visited; + + public boolean possibleBipartition(int n, int[][] dislikes) { + // 图节点编号从 1 开始 + color = new boolean[n + 1]; + visited = new boolean[n + 1]; + // 转化成邻接表表示图结构 + List[] graph = buildGraph(n, dislikes); + + for (int v = 1; v <= n; v++) { + if (!visited[v]) { + dfs(graph, v); + } + } + return ok; + } + + // 建图函数 + private List[] buildGraph(int n, int[][] dislikes) { + // 图节点编号为 1...n + List[] graph = new LinkedList[n + 1]; + for (int i = 1; i <= n; i++) { + graph[i] = new LinkedList<>(); + } + for (int[] edge : dislikes) { + int v = edge[1]; + int w = edge[0]; + // 「无向图」相当于「双向图」 + // v -> w + graph[v].add(w); + // w -> v + graph[w].add(v); + } + return graph; + } + + // 和之前判定二分图的 traverse 函数完全相同 + private void dfs(List[] graph, int v) { + if (!ok) return; + visited[v] = true; + for (int w : graph[v]) { + if (!visited[w]) { + color[w] = !color[v]; + dfs(graph, w); + } else { + if (color[w] == color[v]) { + ok = false; + } + } + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/dfs/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/dfs/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\350\267\257\345\276\204.java" new file mode 100644 index 0000000..b111b38 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/dfs/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\350\267\257\345\276\204.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.graph.dfs; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 797. 所有可能的路径 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 所有可能的路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input = new int[][] { + { 1, 2 }, { 3 }, { 3 }, {} + }; + List> expect = new LinkedList<>(); + expect.add(Arrays.asList(0, 1, 3)); + expect.add(Arrays.asList(0, 2, 3)); + List> output = s.allPathsSourceTarget(input); + for (int i = 0; i < expect.size(); i++) { + Assertions.assertArrayEquals(expect.get(i).toArray(), output.get(i).toArray()); + } + // System.out.println("v = " + output); + } + + static class Solution { + + private List path; + private List> res; + + public List> allPathsSourceTarget(int[][] graph) { + path = new LinkedList<>(); + res = new LinkedList<>(); + dfs(graph, 0); + return res; + } + + // 图的遍历框架 + void dfs(int[][] graph, int s) { + + // 添加节点 s 到路径 + path.add(s); + + int n = graph.length; + if (s == n - 1) { + // 到达终点 + res.add(new LinkedList<>(path)); + path.remove(path.size() - 1); + return; + } + + // 递归每个相邻节点 + for (int v : graph[s]) { + dfs(graph, v); + } + + // 从路径移出节点 s + path.remove(path.size() - 1); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/BFS\351\201\215\345\216\206\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/BFS\351\201\215\345\216\206\345\233\276.java" new file mode 100644 index 0000000..74141ec --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/BFS\351\201\215\345\216\206\345\233\276.java" @@ -0,0 +1,87 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; +import io.github.dunwu.algorithm.graph.State; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 图的遍历框架 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class BFS遍历图 { + + // 图结构的 BFS 遍历,从节点 s 开始进行 BFS + void bfs(Graph graph, int s) { + boolean[] visited = new boolean[graph.size()]; + Queue q = new LinkedList<>(); + q.offer(s); + visited[s] = true; + + while (!q.isEmpty()) { + int cur = q.poll(); + System.out.println("visit " + cur); + for (Edge e : graph.neighbors(cur)) { + if (visited[e.to]) { + continue; + } + q.offer(e.to); + visited[e.to] = true; + } + } + } + + // 从 s 开始 BFS 遍历图的所有节点,且记录遍历的步数 + void bfs2(Graph graph, int s) { + boolean[] visited = new boolean[graph.size()]; + Queue q = new LinkedList<>(); + q.offer(s); + visited[s] = true; + // 记录从 s 开始走到当前节点的步数 + int step = 0; + while (!q.isEmpty()) { + int sz = q.size(); + for (int i = 0; i < sz; i++) { + int cur = q.poll(); + System.out.println("visit " + cur + " at step " + step); + for (Edge e : graph.neighbors(cur)) { + if (visited[e.to]) { + continue; + } + q.offer(e.to); + visited[e.to] = true; + } + } + step++; + } + } + + // 图结构的 BFS 遍历,从节点 s 开始进行 BFS,且记录遍历步数(从起点 s 到当前节点的边的条数) + // 每个节点自行维护 State 类,记录从 s 走来的遍历步数 + void bfs3(Graph graph, int s) { + boolean[] visited = new boolean[graph.size()]; + Queue q = new LinkedList<>(); + + q.offer(new State(s, 0)); + visited[s] = true; + + while (!q.isEmpty()) { + State state = q.poll(); + int node = state.node; + int step = state.step; + System.out.println("visit " + node + " with step " + step); + for (Edge e : graph.neighbors(node)) { + if (visited[e.to]) { + continue; + } + q.offer(new State(e.to, step + 1)); + visited[e.to] = true; + } + } + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\212\202\347\202\271.java" new file mode 100644 index 0000000..86b8925 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\212\202\347\202\271.java" @@ -0,0 +1,47 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; +import io.github.dunwu.algorithm.graph.Vertex; + +/** + * 图的遍历框架 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class DFS遍历图的所有节点 { + + // 遍历图的所有节点 + void traverse(Graph graph, int s, boolean[] visited) { + // base case + if (s < 0 || s >= graph.size()) { return; } + // 防止死循环 + if (visited[s]) { return; } + // 前序位置 + visited[s] = true; + System.out.println("visit " + s); + for (Edge e : graph.neighbors(s)) { + traverse(graph, e.to, visited); + } + // 后序位置 + } + + // 图的遍历框架 + // 需要一个 visited 数组记录被遍历过的节点 + // 避免走回头路陷入死循环 + void traverse(Vertex v, boolean[] visited) { + // base case + if (v == null) { return; } + // 防止死循环 + if (visited[v.id]) { return; } + // 前序位置 + visited[v.id] = true; + System.out.println("visit " + v.id); + for (Vertex neighbor : v.neighbors) { + traverse(neighbor, visited); + } + // 后序位置 + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" new file mode 100644 index 0000000..0a53f86 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; +import io.github.dunwu.algorithm.tree.Node; + +import java.util.LinkedList; + +/** + * DFS遍历图的所有路径 + * + * @author Zhang Peng + * @date 2025-12-02 + */ +public class DFS遍历图的所有路径 { + + // onPath 和 path 记录当前递归路径上的节点 + boolean[] onPath = null; + // 多叉树的遍历框架,寻找从根节点到目标节点的路径 + LinkedList path = new LinkedList<>(); + + void traverse(Node root, Node targetNode) { + // base case + if (root == null) { + return; + } + if (root.val == targetNode.val) { + // 找到目标节点 + System.out.println("find path: " + String.join("->", path) + "->" + targetNode); + return; + } + // 前序位置 + path.addLast(String.valueOf(root.val)); + for (Node child : root.children) { + traverse(child, targetNode); + } + // 后序位置 + path.removeLast(); + } + + void traverse(Graph graph, int from, int to) { + if (onPath == null) { onPath = new boolean[graph.size()]; } + // base case + if (from < 0 || from >= graph.size()) { return; } + // 防止死循环(成环) + if (onPath[from]) { return; } + if (from == to) { + // 找到目标节点 + System.out.println("find path: " + String.join("->", path) + "->" + to); + return; + } + + // 前序位置 + onPath[from] = true; + path.add(String.valueOf(from)); + for (Edge e : graph.neighbors(from)) { + traverse(graph, e.to, to); + } + // 后序位置 + path.remove(path.size() - 1); + onPath[from] = false; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\276\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\276\271.java" new file mode 100644 index 0000000..8081142 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/DFS\351\201\215\345\216\206\345\233\276\347\232\204\346\211\200\346\234\211\350\276\271.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; +import io.github.dunwu.algorithm.graph.Vertex; +import io.github.dunwu.algorithm.tree.Node; + +/** + * DFS遍历图的所有边 + * + * @author Zhang Peng + * @date 2025-11-06 + */ +public class DFS遍历图的所有边 { + + // 遍历多叉树的树枝 + void traverseBranch(Node root) { + // base case + if (root == null) { return; } + for (Node child : root.children) { + System.out.println("visit branch: " + root.val + " -> " + child.val); + traverseBranch(child); + } + } + + // 遍历图的边 + // 需要一个二维 visited 数组记录被遍历过的边,visited[from][to] 表示边 from->to 已经被遍历过 + void traverseEdges(Vertex v, boolean[][] visited) { + // base case + if (v == null) { return; } + for (Vertex neighbor : v.neighbors) { + // 如果边已经被遍历过,则跳过 + if (visited[v.id][neighbor.id]) { continue; } + // 标记并访问边 + visited[v.id][neighbor.id] = true; + System.out.println("visit edge: " + v.id + " -> " + neighbor.id); + traverseEdges(neighbor, visited); + } + } + + // 从起点 s 开始遍历图的所有边 + void traverseEdges(Graph graph, int s, boolean[][] visited) { + // base case + if (s < 0 || s >= graph.size()) { return; } + for (Edge e : graph.neighbors(s)) { + // 如果边已经被遍历过,则跳过 + if (visited[s][e.to]) { continue; } + // 标记并访问边 + visited[s][e.to] = true; + System.out.println("visit edge: " + s + " -> " + e.to); + traverseEdges(graph, e.to, visited); + } + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/Dijkstra.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/Dijkstra.java new file mode 100644 index 0000000..518770b --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/Dijkstra.java @@ -0,0 +1,78 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; +import io.github.dunwu.algorithm.graph.Graph; + +import java.util.Arrays; +import java.util.PriorityQueue; + +/** + * Dijkstra 算法模板 + * + * @author Zhang Peng + * @date 2025-12-03 + */ +public class Dijkstra { + + // 输入不包含负权重边的加权图 graph 和起点 src + // 返回从起点 src 到其他节点的最小路径权重和 + public int[] dijkstra(Graph graph, int src) { + // 记录从起点 src 到其他节点的最小路径权重和 + // distTo[i] 表示从起点 src 到节点 i 的最小路径权重和 + int[] distTo = new int[graph.size()]; + // 都初始化为正无穷,表示未计算 + Arrays.fill(distTo, Integer.MAX_VALUE); + + // 优先级队列,distFromStart 较小的节点排在前面 + PriorityQueue pq = new PriorityQueue<>((a, b) -> { + return a.distFromStart - b.distFromStart; + }); + + // 从起点 src 开始进行 BFS + pq.offer(new State(src, 0)); + distTo[src] = 0; + + while (!pq.isEmpty()) { + State state = pq.poll(); + int curNode = state.node; + int curDistFromStart = state.distFromStart; + + if (distTo[curNode] < curDistFromStart) { + // 在 Dijkstra 算法中,队列中可能存在重复的节点 state + // 所以要在元素出队时进行判断,去除较差的重复节点 + continue; + } + + for (Edge e : graph.neighbors(curNode)) { + int nextNode = e.to; + int nextDistFromStart = curDistFromStart + e.weight; + + if (distTo[nextNode] <= nextDistFromStart) { + continue; + } + + // 将 nextNode 节点加入优先级队列 + pq.offer(new State(nextNode, nextDistFromStart)); + // 记录 nextNode 节点到起点的最小路径权重和 + distTo[nextNode] = nextDistFromStart; + } + } + + return distTo; + } + + static class State { + + // 当前节点 ID + int node; + // 从起点 s 到当前 node 节点的最小路径权重和 + int distFromStart; + + public State(int node, int distFromStart) { + this.node = node; + this.distFromStart = distFromStart; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\345\271\266\346\237\245\351\233\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\345\271\266\346\237\245\351\233\206.java" new file mode 100644 index 0000000..bc15937 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\345\271\266\346\237\245\351\233\206.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.graph.template; + +/** + * 并查集 + * + * @author Zhang Peng + * @date 2025-12-03 + */ +public class 并查集 { + + static class UF { + + // 连通分量个数 + private int count; + // 存储每个节点的父节点 + private int[] parent; + + // n 为图中节点的个数 + public UF(int n) { + this.count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + // 将节点 p 和节点 q 连通 + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + + if (rootP == rootQ) { return; } + + parent[rootQ] = rootP; + // 两个连通分量合并成一个连通分量 + count--; + } + + // 判断节点 p 和节点 q 是否连通 + public boolean connected(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + return rootP == rootQ; + } + + public int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + // 返回图中的连通分量个数 + public int count() { + return count; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\347\237\251\351\230\265\345\256\236\347\216\260\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\347\237\251\351\230\265\345\256\236\347\216\260\345\233\276.java" new file mode 100644 index 0000000..140b44c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\347\237\251\351\230\265\345\256\236\347\216\260\345\233\276.java" @@ -0,0 +1,138 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; + +import java.util.ArrayList; +import java.util.List; + +/** + * 邻接矩阵实现图 + * + * @author Zhang Peng + * @date 2025-11-06 + */ +public class 邻接矩阵实现图 { + + // 加权有向图的通用实现(邻接矩阵) + static class WeightedDigraph { + + // 邻接矩阵,matrix[from][to] 存储从节点 from 到节点 to 的边的权重 + // 0 表示没有连接 + private int[][] matrix; + + public WeightedDigraph(int n) { + matrix = new int[n][n]; + } + + // 增,添加一条带权重的有向边,复杂度 O(1) + public void addEdge(int from, int to, int weight) { + matrix[from][to] = weight; + } + + // 删,删除一条有向边,复杂度 O(1) + public void removeEdge(int from, int to) { + matrix[from][to] = 0; + } + + // 查,判断两个节点是否相邻,复杂度 O(1) + public boolean hasEdge(int from, int to) { + return matrix[from][to] != 0; + } + + // 查,返回一条边的权重,复杂度 O(1) + public int weight(int from, int to) { + return matrix[from][to]; + } + + // 查,返回某个节点的所有邻居节点,复杂度 O(V) + public List neighbors(int v) { + List res = new ArrayList<>(); + for (int i = 0; i < matrix[v].length; i++) { + if (matrix[v][i] != 0) { + res.add(new Edge(i, matrix[v][i])); + } + } + return res; + } + + public static void main(String[] args) { + WeightedDigraph graph = new WeightedDigraph(3); + graph.addEdge(0, 1, 1); + graph.addEdge(1, 2, 2); + graph.addEdge(2, 0, 3); + graph.addEdge(2, 1, 4); + + System.out.println(graph.hasEdge(0, 1)); // true + System.out.println(graph.hasEdge(1, 0)); // false + + graph.neighbors(2).forEach(edge -> { + System.out.println(2 + " -> " + edge.to + ", wight: " + edge.weight); + }); + // 2 -> 0, wight: 3 + // 2 -> 1, wight: 4 + + graph.removeEdge(0, 1); + System.out.println(graph.hasEdge(0, 1)); // false + } + + } + + // 无向加权图的通用实现 + static class WeightedUndigraph { + + private WeightedDigraph graph; + + public WeightedUndigraph(int n) { + graph = new WeightedDigraph(n); + } + + // 增,添加一条带权重的无向边 + public void addEdge(int from, int to, int weight) { + graph.addEdge(from, to, weight); + graph.addEdge(to, from, weight); + } + + // 删,删除一条无向边 + public void removeEdge(int from, int to) { + graph.removeEdge(from, to); + graph.removeEdge(to, from); + } + + // 查,判断两个节点是否相邻 + public boolean hasEdge(int from, int to) { + return graph.hasEdge(from, to); + } + + // 查,返回一条边的权重 + public int weight(int from, int to) { + return graph.weight(from, to); + } + + // 查,返回某个节点的所有邻居节点 + public List neighbors(int v) { + return graph.neighbors(v); + } + + public static void main(String[] args) { + WeightedUndigraph graph = new WeightedUndigraph(3); + graph.addEdge(0, 1, 1); + graph.addEdge(2, 0, 3); + graph.addEdge(2, 1, 4); + + System.out.println(graph.hasEdge(0, 1)); // true + System.out.println(graph.hasEdge(1, 0)); // true + + graph.neighbors(2).forEach(edge -> { + System.out.println(2 + " <-> " + edge.to + ", wight: " + edge.weight); + }); + // 2 <-> 0, wight: 3 + // 2 <-> 1, wight: 4 + + graph.removeEdge(0, 1); + System.out.println(graph.hasEdge(0, 1)); // false + System.out.println(graph.hasEdge(1, 0)); // false + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\350\241\250\345\256\236\347\216\260\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\350\241\250\345\256\236\347\216\260\345\233\276.java" new file mode 100644 index 0000000..2372c4c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/template/\351\202\273\346\216\245\350\241\250\345\256\236\347\216\260\345\233\276.java" @@ -0,0 +1,159 @@ +package io.github.dunwu.algorithm.graph.template; + +import io.github.dunwu.algorithm.graph.Edge; + +import java.util.ArrayList; +import java.util.List; + +/** + * 邻接表实现图 + * + * @author Zhang Peng + * @date 2025-11-06 + */ +public class 邻接表实现图 { + + /** + * 加权有向图的通用实现(邻接表) + */ + static class WeightedDigraph { + + // 邻接表,graph[v] 存储节点 v 的所有邻居节点及对应权重 + private List[] graph; + + public WeightedDigraph(int n) { + // 我们这里简单起见,建图时要传入节点总数,这其实可以优化 + // 比如把 graph 设置为 Map>,就可以动态添加新节点了 + graph = new List[n]; + for (int i = 0; i < n; i++) { + graph[i] = new ArrayList<>(); + } + } + + // 增,添加一条带权重的有向边,复杂度 O(1) + public void addEdge(int from, int to, int weight) { + graph[from].add(new Edge(to, weight)); + } + + // 删,删除一条有向边,复杂度 O(V) + public void removeEdge(int from, int to) { + for (int i = 0; i < graph[from].size(); i++) { + if (graph[from].get(i).to == to) { + graph[from].remove(i); + break; + } + } + } + + // 查,判断两个节点是否相邻,复杂度 O(V) + public boolean hasEdge(int from, int to) { + for (Edge e : graph[from]) { + if (e.to == to) { + return true; + } + } + return false; + } + + // 查,返回一条边的权重,复杂度 O(V) + public int weight(int from, int to) { + for (Edge e : graph[from]) { + if (e.to == to) { + return e.weight; + } + } + throw new IllegalArgumentException("No such edge"); + } + + // 上面的 hasEdge、removeEdge、weight 方法遍历 List 的行为是可以优化的 + // 比如用 Map> 存储邻接表 + // 这样就可以避免遍历 List,复杂度就能降到 O(1) + + // 查,返回某个节点的所有邻居节点,复杂度 O(1) + public List neighbors(int v) { + return graph[v]; + } + + public static void main(String[] args) { + WeightedDigraph graph = new WeightedDigraph(3); + graph.addEdge(0, 1, 1); + graph.addEdge(1, 2, 2); + graph.addEdge(2, 0, 3); + graph.addEdge(2, 1, 4); + + System.out.println(graph.hasEdge(0, 1)); // true + System.out.println(graph.hasEdge(1, 0)); // false + + graph.neighbors(2).forEach(edge -> { + System.out.println(2 + " -> " + edge.to + ", wight: " + edge.weight); + }); + // 2 -> 0, wight: 3 + // 2 -> 1, wight: 4 + + graph.removeEdge(0, 1); + System.out.println(graph.hasEdge(0, 1)); // false + } + + } + + /** + * 无向加权图的通用实现 + */ + static class WeightedUndigraph { + + private WeightedDigraph graph; + + public WeightedUndigraph(int n) { + graph = new WeightedDigraph(n); + } + + // 增,添加一条带权重的无向边 + public void addEdge(int from, int to, int weight) { + graph.addEdge(from, to, weight); + graph.addEdge(to, from, weight); + } + + // 删,删除一条无向边 + public void removeEdge(int from, int to) { + graph.removeEdge(from, to); + graph.removeEdge(to, from); + } + + // 查,判断两个节点是否相邻 + public boolean hasEdge(int from, int to) { + return graph.hasEdge(from, to); + } + + // 查,返回一条边的权重 + public int weight(int from, int to) { + return graph.weight(from, to); + } + + // 查,返回某个节点的所有邻居节点 + public List neighbors(int v) { + return graph.neighbors(v); + } + + public static void main(String[] args) { + WeightedUndigraph graph = new WeightedUndigraph(3); + graph.addEdge(0, 1, 1); + graph.addEdge(2, 0, 3); + graph.addEdge(2, 1, 4); + + System.out.println(graph.hasEdge(0, 1)); // true + System.out.println(graph.hasEdge(1, 0)); // true + + graph.neighbors(2).forEach(edge -> { + System.out.println(2 + " <-> " + edge.to + ", wight: " + edge.weight); + }); + // 2 <-> 0, wight: 3 + // 2 <-> 1, wight: 4 + + graph.removeEdge(0, 1); + System.out.println(graph.hasEdge(0, 1)); // false + System.out.println(graph.hasEdge(1, 0)); // false + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\250.java" new file mode 100644 index 0000000..62c8c0d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\250.java" @@ -0,0 +1,139 @@ +package io.github.dunwu.algorithm.graph.topological_sort; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 207. 课程表 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 课程表 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canFinish(2, new int[][] { { 1, 0 } })); + Assertions.assertFalse(s.canFinish(2, new int[][] { { 1, 0 }, { 0, 1 } })); + + Solution2 s2 = new Solution2(); + Assertions.assertTrue(s2.canFinish(2, new int[][] { { 1, 0 } })); + Assertions.assertFalse(s2.canFinish(2, new int[][] { { 1, 0 }, { 0, 1 } })); + } + + // 环检测算法(DFS 版本) + static class Solution { + + // 记录一次递归堆栈中的节点 + boolean[] onPath; + // 记录节点是否被遍历过 + boolean[] visited; + // 记录图中是否有环 + boolean hasCycle = false; + + public boolean canFinish(int numCourses, int[][] prerequisites) { + List[] graph = buildGraph(numCourses, prerequisites); + visited = new boolean[numCourses]; + onPath = new boolean[numCourses]; + + // 遍历图中的所有节点 + for (int i = 0; i < numCourses; i++) { + dfs(graph, i); + } + // 只要没有循环依赖可以完成所有课程 + return !hasCycle; + } + + public void dfs(List[] graph, int s) { + // 找到环,或已访问,则无需再遍历 + if (onPath[s]) { hasCycle = true; } + if (hasCycle || visited[s]) { return; } + + // 【前序】 + visited[s] = true; + onPath[s] = true; + for (int t : graph[s]) { + dfs(graph, t); + } + // 【后序】 + onPath[s] = false; + } + + public List[] buildGraph(int n, int[][] data) { + List[] graph = new LinkedList[n]; + for (int i = 0; i < n; i++) { + graph[i] = new LinkedList<>(); + } + + for (int[] edge : data) { + int from = edge[1], to = edge[0]; + graph[from].add(to); + } + return graph; + } + + } + + // 环检测算法(BFS 版本) + static class Solution2 { + + public boolean canFinish(int numCourses, int[][] prerequisites) { + // 建图,有向边代表「被依赖」关系 + List[] graph = buildGraph(numCourses, prerequisites); + // 构建入度数组 + int[] indegree = new int[numCourses]; + for (int[] edge : prerequisites) { + int from = edge[1], to = edge[0]; + // 节点 to 的入度加一 + indegree[to]++; + } + + // 根据入度初始化队列中节点 + Queue q = new LinkedList<>(); + for (int i = 0; i < numCourses; i++) { + if (indegree[i] == 0) { + // 节点 i 没有入度,即没有依赖的节点 + // 可以作为拓扑排序的起点,加入队列 + q.offer(i); + } + } + + // 记录遍历的节点个数 + int count = 0; + // 开始执行 BFS 遍历 + while (!q.isEmpty()) { + // 弹出节点 cur,并将它指向的节点的入度减一 + int cur = q.poll(); + count++; + for (int next : graph[cur]) { + indegree[next]--; + if (indegree[next] == 0) { + // 如果入度变为 0,说明 next 依赖的节点都已被遍历 + q.offer(next); + } + } + } + + // 如果所有节点都被遍历过,说明不成环 + return count == numCourses; + } + + public List[] buildGraph(int n, int[][] data) { + List[] graph = new LinkedList[n]; + for (int i = 0; i < n; i++) { + graph[i] = new LinkedList<>(); + } + + for (int[] edge : data) { + int from = edge[1], to = edge[0]; + graph[from].add(to); + } + return graph; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\2502.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\2502.java" new file mode 100644 index 0000000..998b686 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/topological_sort/\350\257\276\347\250\213\350\241\2502.java" @@ -0,0 +1,156 @@ +package io.github.dunwu.algorithm.graph.topological_sort; + +import org.junit.jupiter.api.Assertions; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 210. 课程表 II + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 课程表2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s.findOrder(2, new int[][] { { 1, 0 } })); + Assertions.assertArrayEquals(new int[] { 0, 2, 1, 3 }, + s.findOrder(4, new int[][] { { 1, 0 }, { 2, 0 }, { 3, 1 }, { 3, 2 } })); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new int[] { 0, 1 }, s2.findOrder(2, new int[][] { { 1, 0 } })); + Assertions.assertArrayEquals(new int[] { 0, 2, 1, 3 }, + s2.findOrder(4, new int[][] { { 1, 0 }, { 2, 0 }, { 3, 1 }, { 3, 2 } })); + } + + // 拓扑排序算法(DFS 版本) + static class Solution { + + // 记录后序遍历结果 + private List preorder; + // 记录一次递归堆栈中的节点 + boolean[] onPath; + // 记录节点是否被遍历过 + boolean[] visited; + // 记录图中是否有环 + boolean hasCycle = false; + + public int[] findOrder(int numCourses, int[][] prerequisites) { + List[] graph = buildGraph(numCourses, prerequisites); + preorder = new LinkedList<>(); + visited = new boolean[numCourses]; + onPath = new boolean[numCourses]; + + for (int i = 0; i < numCourses; i++) { + dfs(graph, i); + } + + // 有环图无法进行拓扑排序 + if (hasCycle) { return new int[0]; } + + // 逆后序遍历结果即为拓扑排序结果 + Collections.reverse(preorder); + int[] order = new int[numCourses]; + for (int i = 0; i < numCourses; i++) { + order[i] = preorder.get(i); + } + return order; + } + + public void dfs(List[] graph, int s) { + // 找到环,或已访问,则无需再遍历 + if (onPath[s]) { hasCycle = true; } + if (hasCycle || visited[s]) { return; } + + // 【前序】 + visited[s] = true; + onPath[s] = true; + for (int t : graph[s]) { + dfs(graph, t); + } + // 【后序】 + preorder.add(s); + onPath[s] = false; + } + + public List[] buildGraph(int n, int[][] data) { + List[] graph = new LinkedList[n]; + for (int i = 0; i < n; i++) { + graph[i] = new LinkedList<>(); + } + + for (int[] edge : data) { + int from = edge[1], to = edge[0]; + graph[from].add(to); + } + return graph; + } + + } + + // 拓扑排序算法(BFS 版本) + static class Solution2 { + + public int[] findOrder(int numCourses, int[][] prerequisites) { + // 建图,和环检测算法相同 + List[] graph = buildGraph(numCourses, prerequisites); + // 计算入度,和环检测算法相同 + int[] indegree = new int[numCourses]; + for (int[] edge : prerequisites) { + int from = edge[1], to = edge[0]; + indegree[to]++; + } + + // 根据入度初始化队列中的节点,和环检测算法相同 + Queue q = new LinkedList<>(); + for (int i = 0; i < numCourses; i++) { + if (indegree[i] == 0) { + q.offer(i); + } + } + + // 记录拓扑排序结果 + int[] res = new int[numCourses]; + // 记录遍历节点的顺序(索引) + int count = 0; + // 开始执行 BFS 算法 + while (!q.isEmpty()) { + int cur = q.poll(); + // 弹出节点的顺序即为拓扑排序结果 + res[count] = cur; + count++; + for (int next : graph[cur]) { + indegree[next]--; + if (indegree[next] == 0) { + q.offer(next); + } + } + } + + // 存在环,拓扑排序不存在 + if (count != numCourses) { + return new int[0]; + } + return res; + } + + public List[] buildGraph(int n, int[][] data) { + List[] graph = new LinkedList[n]; + for (int i = 0; i < n; i++) { + graph[i] = new LinkedList<>(); + } + + for (int[] edge : data) { + int from = edge[1], to = edge[0]; + graph[from].add(to); + } + return graph; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\345\206\227\344\275\231\350\277\236\346\216\245.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\345\206\227\344\275\231\350\277\236\346\216\245.java" new file mode 100644 index 0000000..8619903 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\345\206\227\344\275\231\350\277\236\346\216\245.java" @@ -0,0 +1,82 @@ +package io.github.dunwu.algorithm.graph.union_find; + +import org.junit.jupiter.api.Assertions; + +/** + * 684. 冗余连接 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 冗余连接 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[][] input = new int[][] { { 1, 2 }, { 1, 3 }, { 2, 3 } }; + int[][] input2 = new int[][] { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 1, 4 }, { 1, 5 } }; + Assertions.assertArrayEquals(new int[] { 2, 3 }, s.findRedundantConnection(input)); + Assertions.assertArrayEquals(new int[] { 1, 4 }, s.findRedundantConnection(input2)); + } + + static class Solution { + + public int[] findRedundantConnection(int[][] edges) { + UF uf = new UF(edges.length + 1); + for (int[] edge : edges) { + int p = edge[0], q = edge[1]; + if (uf.connected(p, q)) { + return new int[] { p, q }; + } else { + uf.union(p, q); + } + } + return new int[0]; + } + + static class UF { + + // 连通分量个数 + private int count; + // 存储每个节点的父节点 + private int[] parent; + + public UF(int n) { + this.count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + + if (rootP == rootQ) { return; } + + parent[rootQ] = rootP; + count--; + } + + public boolean connected(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + return rootP == rootQ; + } + + public int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + public int count() { + return count; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\347\255\211\345\274\217\346\226\271\347\250\213\347\232\204\345\217\257\346\273\241\350\266\263\346\200\247.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\347\255\211\345\274\217\346\226\271\347\250\213\347\232\204\345\217\257\346\273\241\350\266\263\346\200\247.java" new file mode 100644 index 0000000..c78af57 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\347\255\211\345\274\217\346\226\271\347\250\213\347\232\204\345\217\257\346\273\241\350\266\263\346\200\247.java" @@ -0,0 +1,94 @@ +package io.github.dunwu.algorithm.graph.union_find; + +import org.junit.jupiter.api.Assertions; + +/** + * 990. 等式方程的可满足性 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 等式方程的可满足性 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertFalse(s.equationsPossible(new String[] { "a==b", "b!=a" })); + Assertions.assertTrue(s.equationsPossible(new String[] { "b==a", "a==b" })); + Assertions.assertTrue(s.equationsPossible(new String[] { "a==b", "b==c", "a==c" })); + Assertions.assertFalse(s.equationsPossible(new String[] { "a==b", "b!=c", "c==a" })); + Assertions.assertTrue(s.equationsPossible(new String[] { "c==c", "b==d", "x!=z" })); + } + + static class Solution { + + public boolean equationsPossible(String[] equations) { + UF uf = new UF(26); + for (String exp : equations) { + if (exp.contains("==")) { + String[] vals = exp.split("=="); + int a = vals[0].charAt(0) - 'a'; + int b = vals[1].charAt(0) - 'a'; + uf.union(a, b); + } + } + + for (String exp : equations) { + if (exp.contains("!=")) { + String[] vals = exp.split("!="); + int a = vals[0].charAt(0) - 'a'; + int b = vals[1].charAt(0) - 'a'; + if (uf.connected(a, b)) { + return false; + } + } + } + return true; + } + + static class UF { + + // 连通分量个数 + private int count; + // 存储每个节点的父节点 + private int[] parent; + + public UF(int n) { + this.count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + + if (rootP == rootQ) { return; } + + parent[rootQ] = rootP; + count--; + } + + public boolean connected(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + return rootP == rootQ; + } + + public int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + public int count() { + return count; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.java" new file mode 100644 index 0000000..ae1114a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/graph/union_find/\350\242\253\345\233\264\347\273\225\347\232\204\345\214\272\345\237\237.java" @@ -0,0 +1,136 @@ +package io.github.dunwu.algorithm.graph.union_find; + +import org.junit.jupiter.api.Assertions; + +/** + * 130. 被围绕的区域 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 被围绕的区域 { + + public static void main(String[] args) { + Solution s = new Solution(); + + char[][] input = new char[][] { + { 'X', 'X', 'X', 'X' }, + { 'X', 'O', 'O', 'X' }, + { 'X', 'X', 'O', 'X' }, + { 'X', 'O', 'X', 'X' } + }; + char[][] expect = new char[][] { + { 'X', 'X', 'X', 'X' }, + { 'X', 'X', 'X', 'X' }, + { 'X', 'X', 'X', 'X' }, + { 'X', 'O', 'X', 'X' } + }; + s.solve(input); + Assertions.assertArrayEquals(expect, input); + } + + static class Solution { + + private int m; + private int n; + int[][] direct = new int[][] { { 1, 0 }, { 0, 1 }, { 0, -1 }, { -1, 0 } }; + + public void solve(char[][] board) { + + if (board == null || board.length == 0) return; + m = board.length; + n = board[0].length; + + // 给 dummy 留一个额外位置 + UF uf = new UF(m * n + 1); + int dummy = m * n; + + // 将首列和末列的 O 与 dummy 连通 + for (int i = 0; i < m; i++) { + if (board[i][0] == 'O') { uf.union(index(i, 0), dummy); } + if (board[i][n - 1] == 'O') { uf.union(index(i, n - 1), dummy); } + } + + // 将首行和末行的 O 与 dummy 连通 + for (int j = 0; j < n; j++) { + if (board[0][j] == 'O') { uf.union(index(0, j), dummy); } + if (board[m - 1][j] == 'O') { uf.union(index(m - 1, j), dummy); } + } + + // 方向数组 d 是上下左右搜索的常用手法 + for (int i = 1; i < m - 1; i++) { + for (int j = 1; j < n - 1; j++) { + if (board[i][j] == 'O') { + // 将此 O 与上下左右的 O 连通 + for (int[] d : direct) { + int x = i + d[0], y = j + d[1]; + if (board[x][y] == 'O') { + uf.union(index(x, y), index(i, j)); + } + } + } + } + } + + // 所有不和 dummy 连通的 O,都要被替换 + for (int i = 1; i < m - 1; i++) { + for (int j = 1; j < n - 1; j++) { + int index = index(i, j); + if (!uf.connected(index, dummy)) { + board[i][j] = 'X'; + } + } + } + } + + public int index(int row, int col) { + return row * n + col; + } + + static class UF { + + // 连通分量个数 + private int count; + // 存储每个节点的父节点 + private int[] parent; + + public UF(int n) { + this.count = n; + parent = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + } + } + + public void union(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + + if (rootP == rootQ) { return; } + + parent[rootQ] = rootP; + count--; + } + + public boolean connected(int p, int q) { + int rootP = find(p); + int rootQ = find(q); + return rootP == rootQ; + } + + public int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + public int count() { + return count; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\346\225\260\347\273\204\346\213\206\345\210\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\346\225\260\347\273\204\346\213\206\345\210\206.java" new file mode 100644 index 0000000..88d7ee5 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\346\225\260\347\273\204\346\213\206\345\210\206.java" @@ -0,0 +1,35 @@ +package io.github.dunwu.algorithm.greedy; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 561. 数组拆分 + * + * @author Zhang Peng + * @since 2018-11-05 + */ +public class 数组拆分 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.arrayPairSum(new int[] { 1, 4, 3, 2 })); + Assertions.assertEquals(9, s.arrayPairSum(new int[] { 6, 2, 6, 5, 1, 2 })); + } + + static class Solution { + + + public int arrayPairSum(int[] nums) { + Arrays.sort(nums); + int sum = 0; + for (int i = 0; i < nums.length; i+=2) { + sum += nums[i]; + } + return sum; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\217.java" new file mode 100644 index 0000000..bae6a44 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\217.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.greedy; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; + +/** + * 55. 跳跃游戏 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 跳跃游戏 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, s.jump(new int[] { 2, 3, 1, 1, 4 })); + } + + static class Solution { + + int[] memo; + + public int jump(int[] nums) { + if (nums.length <= 1) { + return 0; + } + int n = nums.length; + // 备忘录都初始化为 n,相当于 INT_MAX + // 因为从 0 跳到 n - 1 最多 n - 1 步 + memo = new int[n]; + Arrays.fill(memo, n); + + return dp(nums, 0); + } + + int dp(int[] nums, int p) { + int n = nums.length; + if (p >= n - 1) { + return 0; + } + + int steps = nums[p]; + // 你可以选择跳 1 步,2 步... + for (int i = 1; i <= steps; i++) { + // 穷举每一个选择 + // 计算每一个子问题的结果 + int sub = dp(nums, p + i); + // 取其中最小的作为最终结果 + memo[p] = Math.min(memo[p], sub + 1); + } + return memo[p]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\2172.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\2172.java" new file mode 100644 index 0000000..d1413bb --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/greedy/\350\267\263\350\267\203\346\270\270\346\210\2172.java" @@ -0,0 +1,31 @@ +package io.github.dunwu.algorithm.greedy; + +import org.junit.jupiter.api.Assertions; + +/** + * 53. 最大子数组和 + * + * @author Zhang Peng + * @date 2025-11-10 + */ +public class 跳跃游戏2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.canJump(new int[] { 2, 3, 1, 1, 4 })); + } + + static class Solution { + + public boolean canJump(int[] nums) { + int farthest = 0; + for (int i = 0; i < nums.length; i++) { + farthest = Math.max(farthest, i + nums[i]); + if (farthest <= i) return false; + } + return farthest >= nums.length - 1; + } + + } + +} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/hash/DesignHashmap.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/DesignHashmap.java similarity index 90% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/hash/DesignHashmap.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/DesignHashmap.java index 1fda12f..8105451 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/hash/DesignHashmap.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/DesignHashmap.java @@ -1,6 +1,7 @@ -package io.github.dunwu.ds.hash; +package io.github.dunwu.algorithm.hash; // 【设计哈希集合】 + // // 不使用任何内建的哈希表库设计一个哈希映射 //// @@ -28,25 +29,21 @@ //// 操作的总数目在[1, 10000]范围内。 //// 不要使用内建的哈希库。 - class DesignHashmap { + private int buckets = 1000; + private int itemsPerBucket = 1001; + private boolean[][] table; - /** Initialize your data structure here. */ + /** + * Initialize your data structure here. + */ public DesignHashmap() { table = new boolean[buckets][]; } - public int hash(int key) { - return key % buckets; - } - - public int pos(int key) { - return key / buckets; - } - public void add(int key) { int hashkey = hash(key); @@ -56,6 +53,14 @@ public void add(int key) { table[hashkey][pos(key)] = true; } + public int hash(int key) { + return key % buckets; + } + + public int pos(int key) { + return key / buckets; + } + public void remove(int key) { int hashkey = hash(key); @@ -64,9 +69,12 @@ public void remove(int key) { } } - /** Returns true if this set did not already contain the specified element */ + /** + * Returns true if this set did not already contain the specified element + */ public boolean contains(int key) { int hashkey = hash(key); return table[hashkey] != null && table[hashkey][pos(key)]; } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/JewelsAndStones.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/JewelsAndStones.java similarity index 97% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/JewelsAndStones.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/JewelsAndStones.java index 5446bbe..57f5933 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/JewelsAndStones.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/JewelsAndStones.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.hashtable; +package io.github.dunwu.algorithm.hash; import java.util.HashSet; @@ -25,6 +25,17 @@ The characters in J are distinct. */ public class JewelsAndStones { + + public static void main(String[] args) { + JewelsAndStones tmpl = new JewelsAndStones(); + + int result1 = tmpl.numJewelsInStones("aA", "aAAbbbb"); + System.out.println("result1 = [" + result1 + "]"); + + int result2 = tmpl.numJewelsInStones("z", "ZZ"); + System.out.println("result1 = [" + result2 + "]"); + } + public int numJewelsInStones(String J, String S) { HashSet set = new HashSet(); for (int i = 0; i < J.length(); i++) { @@ -40,13 +51,4 @@ public int numJewelsInStones(String J, String S) { return count; } - public static void main(String[] args) { - JewelsAndStones tmpl = new JewelsAndStones(); - - int result1 = tmpl.numJewelsInStones("aA", "aAAbbbb"); - System.out.println("result1 = [" + result1 + "]"); - - int result2 = tmpl.numJewelsInStones("z", "ZZ"); - System.out.println("result1 = [" + result2 + "]"); - } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashMap.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashMap.java new file mode 100644 index 0000000..8cf1e6e --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashMap.java @@ -0,0 +1,126 @@ +package io.github.dunwu.algorithm.hash; + +// 不使用任何内建的哈希表库设计一个哈希映射(HashMap)。 +// +// 实现 MyHashMap 类: +// +// MyHashMap() 用空映射初始化对象 +// void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中,则更新其对应的值 value 。 +// int get(int key) 返回特定的 key 所映射的 value ;如果映射中不包含 key 的映射,返回 -1 。 +// void remove(key) 如果映射中存在 key 的映射,则移除 key 和它所对应的 value 。 +// +// 示例: +// +// 输入: +// ["MyHashMap", "put", "put", "get", "get", "put", "get", "remove", "get"] +// [[], [1, 1], [2, 2], [1], [3], [2, 1], [2], [2], [2]] +// 输出: +// [null, null, null, 1, -1, null, 1, null, -1] +// +// 解释: +// MyHashMap myHashMap = new MyHashMap(); +// myHashMap.put(1, 1); // myHashMap 现在为 [[1,1]] +// myHashMap.put(2, 2); // myHashMap 现在为 [[1,1], [2,2]] +// myHashMap.get(1); // 返回 1 ,myHashMap 现在为 [[1,1], [2,2]] +// myHashMap.get(3); // 返回 -1(未找到),myHashMap 现在为 [[1,1], [2,2]] +// myHashMap.put(2, 1); // myHashMap 现在为 [[1,1], [2,1]](更新已有的值) +// myHashMap.get(2); // 返回 1 ,myHashMap 现在为 [[1,1], [2,1]] +// myHashMap.remove(2); // 删除键为 2 的数据,myHashMap 现在为 [[1,1]] +// myHashMap.get(2); // 返回 -1(未找到),myHashMap 现在为 [[1,1]] +// +// 提示: +// +// 0 <= key, value <= 106 +// 最多调用 104 次 put、get 和 remove 方法 +// +// 链接:https://leetcode-cn.com/leetbook/read/hash-table/xhqwd3/ + +import java.util.LinkedList; + +/** + * 实现 HashMap,使用拉链表法(基于 LinkedList 实现)决哈希冲突 + * + * @author Zhang Peng + * @since 2022-03-20 + */ +public class MyHashMap { + + private final int BUCKET_NUM = 1000; + private final LinkedList[] data; + + public MyHashMap() { + data = new LinkedList[BUCKET_NUM]; + for (int i = 0; i < BUCKET_NUM; ++i) { + data[i] = new LinkedList<>(); + } + } + + public void put(int key, int value) { + int bucket = hash(key); + for (Pair pair : data[bucket]) { + if (pair.key == key) { + pair.value = value; + return; + } + } + data[bucket].add(new Pair(key, value)); + } + + public int get(int key) { + int bucket = hash(key); + for (Pair pair : data[bucket]) { + if (pair.key == key) { + return pair.value; + } + } + return -1; + } + + public void remove(int key) { + int bucket = hash(key); + for (Pair pair : data[bucket]) { + if (pair.key == key) { + data[bucket].remove(pair); + return; + } + } + } + + private int hash(int key) { + return key % BUCKET_NUM; + } + + private static class Pair { + + private final int key; + private int value; + + public Pair(int key, int value) { + this.key = key; + this.value = value; + } + + public int getKey() { + return key; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + } + + public static void main(String[] args) { + MyHashMap obj = new MyHashMap(); + obj.put(5, 555); + obj.put(1005, 555); + System.out.println("key = 5, value = " + obj.get(5)); + System.out.println("key = 1005, value = " + obj.get(1005)); + // obj.remove(key); + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet.java new file mode 100644 index 0000000..1a864d5 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet.java @@ -0,0 +1,90 @@ +// 【设计哈希集合】 +// +// 不使用任何内建的哈希表库设计一个哈希集合 +// +// 具体地说,你的设计应该包含以下的功能 +// +// add(value):向哈希集合中插入一个值。 +// contains(value) :返回哈希集合中是否存在这个值。 +// remove(value):将给定值从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。 +// +// 示例: +// +// MyHashSet hashSet = new MyHashSet(); +// hashSet.add(1); +// hashSet.add(2); +// hashSet.contains(1); // 返回 true +// hashSet.contains(3); // 返回 false (未找到) +// hashSet.add(2); +// hashSet.contains(2); // 返回 true +// hashSet.remove(2); +// hashSet.contains(2); // 返回 false (已经被删除) +// +// 注意: +// +// 所有的值都在 [1, 1000000]的范围内。 +// 操作的总数目在[1, 10000]范围内。 +// 不要使用内建的哈希集合库。 + +package io.github.dunwu.algorithm.hash; + +import java.util.LinkedList; + +/** + * 实现 HashMap,使用拉链表法(基于 LinkedList 实现)决哈希冲突 + * + * @author Zhang Peng + * @since 2022-03-20 + */ +class MyHashSet { + + private final int BUCKET_NUM = 1000; + + private final LinkedList[] data; + + public MyHashSet() { + data = new LinkedList[BUCKET_NUM]; + for (int i = 0; i < BUCKET_NUM; ++i) { + data[i] = new LinkedList<>(); + } + } + + public void add(int key) { + int bucket = hash(key); + for (Integer item : data[bucket]) { + if (item == key) { + return; + } + } + data[bucket].add(key); + } + + public int hash(int key) { + return key % BUCKET_NUM; + } + + public int pos(int key) { + return key / BUCKET_NUM; + } + + public void remove(int key) { + int bucket = hash(key); + for (Integer item : data[bucket]) { + if (item == key) { + data[bucket].remove(item); + return; + } + } + } + + public boolean contains(int key) { + int bucket = hash(key); + for (Integer item : data[bucket]) { + if (item == key) { + return true; + } + } + return false; + } + +} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/hash/DesignHashset.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet2.java similarity index 58% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/hash/DesignHashset.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet2.java index e284d96..dc60c47 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/hash/DesignHashset.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/MyHashSet2.java @@ -1,5 +1,3 @@ -package io.github.dunwu.ds.hash; - // 【设计哈希集合】 // // 不使用任何内建的哈希表库设计一个哈希集合 @@ -28,45 +26,53 @@ // 操作的总数目在[1, 10000]范围内。 // 不要使用内建的哈希集合库。 +package io.github.dunwu.algorithm.hash; -class DesignHashset { - private int buckets = 1000; - private int itemsPerBucket = 1001; - private boolean[][] table; +/** + * 实现 HashSet,使用开放寻址法决哈希冲突 + * + * @author Zhang Peng + * @since 2022-03-20 + */ +class MyHashSet2 { - /** Initialize your data structure here. */ - public DesignHashset() { - table = new boolean[buckets][]; - } + private final int BUCKET_NUM = 1000; - public int hash(int key) { - return key % buckets; - } + private final boolean[][] data; - public int pos(int key) { - return key / buckets; + public MyHashSet2() { + data = new boolean[BUCKET_NUM][]; } public void add(int key) { - int hashkey = hash(key); + int bucket = hash(key); - if (table[hashkey] == null) { - table[hashkey] = new boolean[itemsPerBucket]; + if (data[bucket] == null) { + // BUCKET_NUM + 1 是为了防止数组越界 + data[bucket] = new boolean[BUCKET_NUM + 1]; } - table[hashkey][pos(key)] = true; + data[bucket][pos(key)] = true; + } + + public int hash(int key) { + return key % BUCKET_NUM; + } + + public int pos(int key) { + return key / BUCKET_NUM; } public void remove(int key) { - int hashkey = hash(key); + int bucket = hash(key); - if (table[hashkey] != null) { - table[hashkey][pos(key)] = false; + if (data[bucket] != null) { + data[bucket][pos(key)] = false; } } - /** Returns true if this set did not already contain the specified element */ public boolean contains(int key) { - int hashkey = hash(key); - return table[hashkey] != null && table[hashkey][pos(key)]; + int bucket = hash(key); + return data[bucket] != null && data[bucket][pos(key)]; } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/SubdomainVisitCount.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/SubdomainVisitCount.java similarity index 88% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/SubdomainVisitCount.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/SubdomainVisitCount.java index 878ed25..7d7f733 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/SubdomainVisitCount.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/SubdomainVisitCount.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.hashtable; +package io.github.dunwu.algorithm.hash; import java.util.ArrayList; import java.util.HashMap; @@ -47,6 +47,15 @@ */ public class SubdomainVisitCount { + public static void main(String[] args) { + SubdomainVisitCount tmpl = new SubdomainVisitCount(); + + String[] s1 = new String[] { "9001 discuss.leetcode.com" }; + String[] s2 = new String[] { "900 google.mail.com", "50 yahoo.com", "1 intel.mail.com", "5 wiki.org" }; + tmpl.subdomainVisits(s1); + tmpl.subdomainVisits(s2); + } + public List subdomainVisits(String[] cpdomains) { List result = new ArrayList<>(); Map map = new HashMap<>(); // key: subdomain, value: frequency @@ -59,10 +68,10 @@ public List subdomainVisits(String[] cpdomains) { resultStringBuilder.setLength(0); resultStringBuilder.append(domain); while (true) { - map.put(resultStringBuilder.toString(), map.getOrDefault(resultStringBuilder.toString(), 0) + numClicks); + map.put(resultStringBuilder.toString(), + map.getOrDefault(resultStringBuilder.toString(), 0) + numClicks); int dotPosition = resultStringBuilder.indexOf("."); - if (dotPosition == -1) - break; + if (dotPosition == -1) { break; } resultStringBuilder.delete(0, dotPosition + 1); } } @@ -73,12 +82,4 @@ public List subdomainVisits(String[] cpdomains) { return result; } - public static void main(String[] args) { - SubdomainVisitCount tmpl = new SubdomainVisitCount(); - - String[] s1 = new String[]{"9001 discuss.leetcode.com"}; - String[] s2 = new String[]{"900 google.mail.com", "50 yahoo.com", "1 intel.mail.com", "5 wiki.org"}; - tmpl.subdomainVisits(s1); - tmpl.subdomainVisits(s2); - } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/ToLowerCase.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/ToLowerCase.java similarity index 95% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/ToLowerCase.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/ToLowerCase.java index 647b34e..97af3d7 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hashtable/ToLowerCase.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/hash/ToLowerCase.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.hashtable; +package io.github.dunwu.algorithm.hash; /* https://leetcode.com/problems/to-lower-case/ @@ -22,6 +22,13 @@ Implement function ToLowerCase() that has a string parameter str, and returns th */ public class ToLowerCase { + + public static void main(String[] args) { + ToLowerCase tmpl = new ToLowerCase(); + String result = tmpl.toLowerCase("Hello"); + System.out.println("result = [" + result + "]"); + } + public String toLowerCase(String str) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < str.length(); i++) { @@ -34,9 +41,4 @@ public String toLowerCase(String str) { return sb.toString(); } - public static void main(String[] args) { - ToLowerCase tmpl = new ToLowerCase(); - String result = tmpl.toLowerCase("Hello"); - System.out.println("result = [" + result + "]"); - } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/heap/KthLargest.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/heap/KthLargest.java new file mode 100644 index 0000000..57a726f --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/heap/KthLargest.java @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.heap; + +import java.util.PriorityQueue; + +// 703. 数据流中的第K大元素 +// +// 设计一个找到数据流中第K大元素的类(class)。注意是排序后的第K大元素,不是第K个不同的元素。 +// +// 你的 KthLargest 类需要一个同时接收整数 k 和整数数组nums 的构造器,它包含数据流中的初始元素。每次调用 KthLargest.add,返回当前数据流中第K大的元素。 +// +// 示例: +// +// int k = 3; +// int[] arr = [4,5,8,2]; +// KthLargest kthLargest = new KthLargest(3, arr); +// kthLargest.add(3);   // returns 4 +// kthLargest.add(5);   // returns 5 +// kthLargest.add(10);  // returns 5 +// kthLargest.add(9);   // returns 8 +// kthLargest.add(4);   // returns 8 +// 说明: +// 你可以假设 nums 的长度≥ k-1 且k ≥ 1。 + +public class KthLargest> { + + private int size; + + private PriorityQueue queue; + + public KthLargest(int k, T[] nums) { + size = k; + queue = new PriorityQueue<>(k); + for (T num : nums) { + add(num); + } + } + + public T add(T val) { + if (queue.size() < size) { + queue.add(val); + } else if (queue.peek().compareTo(val) < 0) { + queue.poll(); + queue.add(val); + } + return queue.peek(); + } + + public static void main(String[] args) { + Integer[] data = new Integer[] { 4, 5, 8, 2 }; + KthLargest demo = new KthLargest<>(3, data); + System.out.println("args = " + demo.add(3)); + System.out.println("args = " + demo.add(5)); + System.out.println("args = " + demo.add(10)); + System.out.println("args = " + demo.add(9)); + System.out.println("args = " + demo.add(4)); + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/heap/KthLeast.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/heap/KthLeast.java new file mode 100644 index 0000000..e2110a8 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/heap/KthLeast.java @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.heap; + +import java.util.PriorityQueue; + +/** + * @author Zhang Peng + * @since 2020-03-06 + */ +public class KthLeast> { + + private int size; + + private PriorityQueue queue; + + public KthLeast(int k, T[] array) { + this.size = k; + queue = new PriorityQueue<>(k); + for (T v : array) { + add(v); + } + } + + public T add(T val) { + if (queue.size() < size) { + queue.add(val); + } else if (queue.peek().compareTo(val) > 0) { + queue.poll(); + queue.add(val); + } + return queue.peek(); + } + + public T pop() { + return queue.poll(); + } + + public static void main(String[] args) { + Integer[] data = new Integer[] { 3, 2, 1 }; + KthLeast demo = new KthLeast<>(2, data); + System.out.println("args = " + demo.pop()); + System.out.println("args = " + demo.pop()); + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/heap/LeastKNum.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/heap/LeastKNum.java new file mode 100644 index 0000000..9719c08 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/heap/LeastKNum.java @@ -0,0 +1,29 @@ +package io.github.dunwu.algorithm.heap; + +/** + * 面试题40. 最小的k个数 + *

+ * 输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。 + *

+ *   + *

+ * 示例 1: + *

+ * 输入:arr = [3,2,1], k = 2 输出:[1,2] 或者 [2,1] 示例 2: + *

+ * 输入:arr = [0,1,2,1], k = 1 输出:[0]   + *

+ * 限制: + *

+ * 0 <= k <= arr.length <= 10000 0 <= arr[i] <= 10000 + * + * @author Zhang Peng + * @since 2020-03-06 + */ +public class LeastKNum { + + // public int[] getLeastNumbers(int[] arr, int k) { + // + // } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/ListNode.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/ListNode.java new file mode 100644 index 0000000..f8868a7 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/ListNode.java @@ -0,0 +1,114 @@ +package io.github.dunwu.algorithm.linkedlist; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public final class ListNode { + + public int val; + public ListNode next; + + public ListNode(int val) { this.val = val; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ListNode)) return false; + ListNode listNode = (ListNode) o; + return val == listNode.val && + Objects.equals(next, listNode.next); + } + + @Override + public int hashCode() { + return Objects.hash(val, next); + } + + public List toList() { + return ListNode.toList(this); + } + + public static ListNode createLinkedList(int[] arr) { + if (arr == null || arr.length == 0) { + return null; + } + ListNode head = new ListNode(arr[0]); + ListNode cur = head; + for (int i = 1; i < arr.length; i++) { + cur.next = new ListNode(arr[i]); + cur = cur.next; + } + return head; + } + + public static void addLast() { + + } + + public static ListNode buildList(int... list) { + ListNode head = new ListNode(-1); + ListNode node = head; + for (int val : list) { + node.next = new ListNode(val); + node = node.next; + } + return head.next; + } + + public static ListNode buildCycleList(int cyclePoint, int[] list) { + ListNode head = new ListNode(-1); + ListNode node = head; + ListNode cycleBeginNode = null; + for (int val : list) { + ListNode item = new ListNode(val); + if (cyclePoint == 0 && cycleBeginNode == null) { + cycleBeginNode = item; + } else { + cyclePoint--; + } + node.next = item; + node = node.next; + } + if (cycleBeginNode != null) { + node.next = cycleBeginNode; + } + return head.next; + } + + public static List toList(ListNode listNode) { + List list = new ArrayList<>(); + while (listNode != null) { + list.add(listNode.val); + listNode = listNode.next; + } + return list; + } + + public static void buildMetPot(ListNode listA, ListNode listB, int skipA, int skipB) { + ListNode pA = listA; + for (int i = 0; i < skipA; i++) { + pA = pA.next; + } + ListNode pB = listB; + for (int i = 0; i < skipB - 1; i++) { + pB = pB.next; + } + pB.next = pA; + } + + public static void main(String[] args) { + int[] arr = { 1, 2, 3, 4, 5 }; + ListNode head = createLinkedList(arr); + ListNode p = head; + while (p.next != null) { + p = p.next; + } + p.next = new ListNode(6); + while (head != null) { + System.out.println(head.val); + head = head.next; + } + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\344\272\214\350\277\233\345\210\266\351\223\276\350\241\250\350\275\254\346\225\264\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\344\272\214\350\277\233\345\210\266\351\223\276\350\241\250\350\275\254\346\225\264\346\225\260.java" new file mode 100644 index 0000000..2afe676 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\344\272\214\350\277\233\345\210\266\351\223\276\350\241\250\350\275\254\346\225\264\346\225\260.java" @@ -0,0 +1,34 @@ +package io.github.dunwu.algorithm.linkedlist.base; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 二进制链表转整数 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 二进制链表转整数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.getDecimalValue(ListNode.buildList(1, 0, 1))); + Assertions.assertEquals(0, s.getDecimalValue(ListNode.buildList(0))); + } + + static class Solution { + + public int getDecimalValue(ListNode head) { + int res = 0; + ListNode p = head; + while (p != null) { + res = res * 2 + p.val; + p = p.next; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.java" new file mode 100644 index 0000000..97faea6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/base/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.java" @@ -0,0 +1,42 @@ +package io.github.dunwu.algorithm.linkedlist.base; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * LCR 136. 删除链表的节点 + * + * @author Zhang Peng + * @date 2025-12-18 + */ +public class 删除链表的节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(4, 5, 1), s.deleteNode(ListNode.buildList(4, 5, 1, 9), 9)); + Assertions.assertEquals(ListNode.buildList(4, 1, 9), s.deleteNode(ListNode.buildList(4, 5, 1, 9), 5)); + Assertions.assertEquals(ListNode.buildList(4, 5, 9), s.deleteNode(ListNode.buildList(4, 5, 1, 9), 1)); + Assertions.assertEquals(ListNode.buildList(5, 1, 9), s.deleteNode(ListNode.buildList(4, 5, 1, 9), 4)); + } + + static class Solution { + + public ListNode deleteNode(ListNode head, int val) { + ListNode dummy = new ListNode(-1); + dummy.next = head; + ListNode pre = dummy; + while (pre != null && pre.next != null) { + ListNode cur = pre.next; + if (cur.val == val) { + pre.next = cur.next; + pre = cur.next; + } else { + pre = pre.next; + } + } + return dummy.next; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/DoublyLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/DoublyLinkedList.java new file mode 100644 index 0000000..9b4c126 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/DoublyLinkedList.java @@ -0,0 +1,230 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +public class DoublyLinkedList { + + private int size = 0; + private Node first = null; + private Node last = null; + + public DoublyLinkedList() { + first = new Node<>(null); + last = new Node<>(null); + first.next = last; + last.prev = first; + } + + public int size() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + public int indexOf(E element) { + int pos = 0; + + Node p = first.next; + while (p != null) { + if (p.element.equals(element)) { + return pos; + } + p = p.next; + pos++; + } + return -1; + } + + public E get(int index) { + Node node = node(index); + return node == null ? null : node.element; + } + + Node node(int index) { + int i = 0; + + Node p = first; + while (p != null && i != index) { + p = p.next; + i++; + } + return p; + } + + public void addFirst(E element) { + + Node node = new Node<>(element); + Node temp = first.next; + + node.next = temp; + temp.prev = node; + + node.prev = first; + first.next = node; + + size++; + } + + public void addLast(E element) { + + Node node = new Node<>(element); + Node temp = last.prev; + + temp.next = node; + node.prev = temp; + + last.prev = node; + node.next = last; + + size++; + } + + public boolean add(int index, E element) { + + if (index == 0) { + addFirst(element); + return true; + } + + Node p = node(index); + Node node = new Node<>(element); + node.next = p.next; + p.next.prev = node; + + node.prev = p; + p.next = node; + + size++; + return true; + } + + public boolean remove(E e) { + if (e == null) { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element == null) { + p.next = x.next; + size--; + return true; + } + p = p.next; + } + } else { + Node p = first; + while (p != null && p.next != null) { + Node x = p.next; + if (e.equals(x.element)) { + p.next = x.next; + size--; + return true; + } + p = p.next; + } + } + return false; + } + + public boolean removeAll(E e) { + + if (first.next == null) { + return false; + } + + if (e == null) { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element == null) { + p.next = x.next; + size--; + } + p = p.next; + } + } else { + Node p = first; + while (p != null && p.next != null) { + if (e.equals(p.next.element)) { + p.next = p.next.next; + size--; + } else { + p = p.next; + } + } + } + return true; + } + + public void clear() { + first.next = last; + last.prev = first; + size = 0; + } + + public void printAll() { + Node p = first; + while (p.next != null && p.next != last) { + p = p.next; + System.out.print(p.element + " "); + } + System.out.println(); + } + + public List toList() { + List list = new ArrayList<>(); + Node node = first.next; + while (node != null && node != last) { + list.add(node.element); + node = node.next; + } + return list; + } + + @Getter + @Setter + public static class Node { + + private E element; + private Node next; + private Node prev; + + public Node(E element) { + this.element = element; + this.next = null; + this.prev = null; + } + + public Node(E element, Node prev, Node next) { + this.element = element; + this.prev = prev; + this.next = next; + } + + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 3, 4, 5 }; + DoublyLinkedList list = new DoublyLinkedList<>(); + DoublyLinkedList reverseList = new DoublyLinkedList<>(); + for (int num : nums) { + list.addLast(num); + reverseList.addFirst(num); + } + + list.add(5, 999); + System.out.println("【队尾写入链表】"); + list.printAll(); + System.out.println("【队头写入链表】"); + reverseList.printAll(); + list.printAll(); + System.out.println("999 在队列中的位置:" + list.indexOf(999)); + System.out.println("队列中位置 5 的元素值:" + list.get(5)); + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBaseLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBaseLinkedList.java new file mode 100644 index 0000000..de8e751 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBaseLinkedList.java @@ -0,0 +1,177 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +import java.util.Scanner; + +/** + * 基于单链表LRU算法(java) + * + * @author hoda + * @create 2018-12-17 + */ +public class LRUBaseLinkedList { + + /** + * 默认链表容量 + */ + private final static Integer DEFAULT_CAPACITY = 10; + + /** + * 头结点 + */ + private Node headNode; + + /** + * 链表长度 + */ + private Integer length; + + /** + * 链表容量 + */ + private Integer capacity; + + public LRUBaseLinkedList() { + this.headNode = new Node<>(); + this.capacity = DEFAULT_CAPACITY; + this.length = 0; + } + + public LRUBaseLinkedList(Integer capacity) { + this.headNode = new Node<>(); + this.capacity = capacity; + this.length = 0; + } + + public void add(T data) { + Node preNode = findPreNode(data); + + // 链表中存在,删除原数据,再插入到链表的头部 + if (preNode != null) { + deleteElemOptim(preNode); + intsertElemAtBegin(data); + } else { + if (length >= this.capacity) { + //删除尾结点 + deleteElemAtEnd(); + } + intsertElemAtBegin(data); + } + } + + /** + * 删除preNode结点下一个元素 + * + * @param preNode + */ + private void deleteElemOptim(Node preNode) { + Node temp = preNode.getNext(); + preNode.setNext(temp.getNext()); + temp = null; + length--; + } + + /** + * 链表头部插入节点 + * + * @param data + */ + private void intsertElemAtBegin(T data) { + Node next = headNode.getNext(); + headNode.setNext(new Node<>(data, next)); + length++; + } + + /** + * 获取查找到元素的前一个结点 + * + * @param data + * @return + */ + private Node findPreNode(T data) { + Node node = headNode; + while (node.getNext() != null) { + if (data.equals(node.getNext().getElement())) { + return node; + } + node = node.getNext(); + } + return null; + } + + /** + * 删除尾结点 + */ + private void deleteElemAtEnd() { + Node ptr = headNode; + // 空链表直接返回 + if (ptr.getNext() == null) { + return; + } + + // 倒数第二个结点 + while (ptr.getNext().getNext() != null) { + ptr = ptr.getNext(); + } + + Node tmp = ptr.getNext(); + ptr.setNext(null); + tmp = null; + length--; + } + + private void printAll() { + Node node = headNode.getNext(); + while (node != null) { + System.out.print(node.getElement() + ","); + node = node.getNext(); + } + System.out.println(); + } + + public static class Node { + + private T element; + + private Node next; + + public Node(T element) { + this.element = element; + } + + public Node(T element, Node next) { + this.element = element; + this.next = next; + } + + public Node() { + this.next = null; + } + + public T getElement() { + return element; + } + + public void setElement(T element) { + this.element = element; + } + + public Node getNext() { + return next; + } + + public void setNext(Node next) { + this.next = next; + } + + } + + public static void main(String[] args) { + LRUBaseLinkedList list = new LRUBaseLinkedList<>(); + Scanner sc = new Scanner(System.in); + while (true) { + list.add(sc.nextInt()); + list.printAll(); + } + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBasedArray.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBasedArray.java new file mode 100644 index 0000000..cd909e4 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/LRUBasedArray.java @@ -0,0 +1,174 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +import java.util.HashMap; +import java.util.Map; +/** + * Created by SpecialYang in 2018/12/7 2:00 PM. + * + * 基于数组实现的LRU缓存 + * 1. 空间复杂度为O(n) + * 2. 时间复杂度为O(n) + * 3. 不支持null的缓存 + */ +public class LRUBasedArray { + + private static final int DEFAULT_CAPACITY = (1 << 3); + + private int capacity; + + private int count; + + private T[] value; + + private Map holder; + + public LRUBasedArray() { + this(DEFAULT_CAPACITY); + } + + public LRUBasedArray(int capacity) { + this.capacity = capacity; + value = (T[]) new Object[capacity]; + count = 0; + holder = new HashMap(capacity); + } + + /** + * 模拟访问某个值 + * @param object + */ + public void offer(T object) { + if (object == null) { + throw new IllegalArgumentException("该缓存容器不支持null!"); + } + Integer index = holder.get(object); + if (index == null) { + if (isFull()) { + removeAndCache(object); + } else { + cache(object, count); + } + } else { + update(index); + } + } + + /** + * 若缓存中有指定的值,则更新位置 + * @param end + */ + public void update(int end) { + T target = value[end]; + rightShift(end); + value[0] = target; + holder.put(target, 0); + } + + /** + * 缓存数据到头部,但要先右移 + * @param object + * @param end 数组右移的边界 + */ + public void cache(T object, int end) { + rightShift(end); + value[0] = object; + holder.put(object, 0); + count++; + } + + /** + * 缓存满的情况,踢出后,再缓存到数组头部 + * @param object + */ + public void removeAndCache(T object) { + T key = value[--count]; + holder.remove(key); + cache(object, count); + } + + /** + * end左边的数据统一右移一位 + * @param end + */ + private void rightShift(int end) { + for (int i = end - 1; i >= 0; i--) { + value[i + 1] = value[i]; + holder.put(value[i], i + 1); + } + } + + public boolean isContain(T object) { + return holder.containsKey(object); + } + + public boolean isEmpty() { + return count == 0; + } + + public boolean isFull() { + return count == capacity; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; i++) { + sb.append(value[i]); + sb.append(" "); + } + return sb.toString(); + } + + static class TestLRUBasedArray { + + public static void main(String[] args) { + testDefaultConstructor(); + testSpecifiedConstructor(4); +// testWithException(); + } + + private static void testWithException() { + LRUBasedArray lru = new LRUBasedArray(); + lru.offer(null); + } + + public static void testDefaultConstructor() { + System.out.println("======无参测试========"); + LRUBasedArray lru = new LRUBasedArray(); + lru.offer(1); + lru.offer(2); + lru.offer(3); + lru.offer(4); + lru.offer(5); + System.out.println(lru); + lru.offer(6); + lru.offer(7); + lru.offer(8); + lru.offer(9); + System.out.println(lru); + } + + public static void testSpecifiedConstructor(int capacity) { + System.out.println("======有参测试========"); + LRUBasedArray lru = new LRUBasedArray(capacity); + lru.offer(1); + System.out.println(lru); + lru.offer(2); + System.out.println(lru); + lru.offer(3); + System.out.println(lru); + lru.offer(4); + System.out.println(lru); + lru.offer(2); + System.out.println(lru); + lru.offer(4); + System.out.println(lru); + lru.offer(7); + System.out.println(lru); + lru.offer(1); + System.out.println(lru); + lru.offer(2); + System.out.println(lru); + } + } +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/MyLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/MyLinkedList.java new file mode 100644 index 0000000..be76a54 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/MyLinkedList.java @@ -0,0 +1,244 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +import java.util.NoSuchElementException; + +public class MyLinkedList { + + // 虚拟头尾节点 + private int size; + final private Node head, tail; + + // 双链表节点 + private static class Node { + + E data; + Node next; + Node prev; + + Node(E data) { + this.data = data; + } + + } + + // 构造函数初始化虚拟头尾节点 + public MyLinkedList() { + this.head = new Node<>(null); + this.tail = new Node<>(null); + head.next = tail; + tail.prev = head; + this.size = 0; + } + + // ***** 增 ***** + + public void addLast(T data) { + Node node = new Node<>(data); + Node temp = tail.prev; + // temp <-> x + temp.next = node; + node.prev = temp; + + node.next = tail; + tail.prev = node; + // temp <-> x <-> tail + size++; + } + + public void addFirst(T e) { + Node x = new Node<>(e); + Node temp = head.next; + // head <-> temp + temp.prev = x; + x.next = temp; + + head.next = x; + x.prev = head; + // head <-> x <-> temp + size++; + } + + public void add(int index, T element) { + checkPositionIndex(index); + if (index == size) { + addLast(element); + return; + } + + // 找到 index 对应的 Node + Node p = getNode(index); + Node temp = p.prev; + // temp <-> p + + // 新要插入的 Node + Node x = new Node<>(element); + + p.prev = x; + temp.next = x; + + x.prev = temp; + x.next = p; + + // temp <-> x <-> p + + size++; + } + + // ***** 删 ***** + + public T removeFirst() { + if (size < 1) { + throw new NoSuchElementException(); + } + // 虚拟节点的存在是我们不用考虑空指针的问题 + Node x = head.next; + Node temp = x.next; + // head <-> x <-> temp + head.next = temp; + temp.prev = head; + + x.prev = null; + x.next = null; + // head <-> temp + + size--; + return x.data; + } + + public T removeLast() { + if (size < 1) { + throw new NoSuchElementException(); + } + Node x = tail.prev; + Node temp = tail.prev.prev; + // temp <-> x <-> tail + + tail.prev = temp; + temp.next = tail; + + x.prev = null; + x.next = null; + // temp <-> tail + + size--; + return x.data; + } + + public T remove(int index) { + checkElementIndex(index); + // 找到 index 对应的 Node + Node x = getNode(index); + Node prev = x.prev; + Node next = x.next; + // prev <-> x <-> next + prev.next = next; + next.prev = prev; + + x.prev = x.next = null; + // prev <-> next + + size--; + + return x.data; + } + + // ***** 查 ***** + + public T get(int index) { + checkElementIndex(index); + // 找到 index 对应的 Node + Node p = getNode(index); + + return p.data; + } + + public T getFirst() { + if (size < 1) { + throw new NoSuchElementException(); + } + + return head.next.data; + } + + public T getLast() { + if (size < 1) { + throw new NoSuchElementException(); + } + + return tail.prev.data; + } + + // ***** 改 ***** + + public T set(int index, T val) { + checkElementIndex(index); + // 找到 index 对应的 Node + Node p = getNode(index); + + T oldVal = p.data; + p.data = val; + + return oldVal; + } + + // ***** 其他工具函数 ***** + + public int size() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + private Node getNode(int index) { + checkElementIndex(index); + Node p = head.next; + // TODO: 可以优化,通过 index 判断从 head 还是 tail 开始遍历 + for (int i = 0; i < index; i++) { + p = p.next; + } + return p; + } + + private boolean isElementIndex(int index) { + return index >= 0 && index < size; + } + + private boolean isPositionIndex(int index) { + return index >= 0 && index <= size; + } + + // 检查 index 索引位置是否可以存在元素 + private void checkElementIndex(int index) { + if (!isElementIndex(index)) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } + } + + // 检查 index 索引位置是否可以添加元素 + private void checkPositionIndex(int index) { + if (!isPositionIndex(index)) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } + } + + private void display() { + System.out.println("size = " + size); + for (Node p = head.next; p != tail; p = p.next) { + System.out.print(p.data + " <-> "); + } + System.out.println("null"); + System.out.println(); + } + + public static void main(String[] args) { + MyLinkedList list = new MyLinkedList<>(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.addFirst(0); + list.add(2, 100); + + list.display(); + // size = 5 + // 0 <-> 1 <-> 100 <-> 2 -> 3 -> null + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/SinglyLinkedList.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/SinglyLinkedList.java new file mode 100644 index 0000000..d519205 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/SinglyLinkedList.java @@ -0,0 +1,264 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +public class SinglyLinkedList { + + private int size = 0; + private Node first = null; + + public SinglyLinkedList() { + this.size = 0; + this.first = new Node<>(null); + } + + public SinglyLinkedList(E[] elementArray) { + this.size = 0; + this.first = new Node<>(null); + for (E element : elementArray) { + addLast(element); + } + } + + public int size() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + public int indexOf(E element) { + int pos = 0; + Node p = first.next; + while (p != null) { + if (p.element.equals(element)) { + return pos; + } + p = p.next; + pos++; + } + return -1; + } + + public E get(int index) { + Node node = node(index); + return node == null ? null : node.element; + } + + Node node(int index) { + int i = 0; + Node p = first; + while (p != null && i != index) { + p = p.next; + i++; + } + return p; + } + + public void addFirst(E element) { + Node node = new Node<>(element); + node.next = first.next; + first.next = node; + size++; + } + + public void addLast(E element) { + Node node = new Node<>(element); + Node p = first; + while (p.next != null) { + p = p.next; + } + p.next = node; + size++; + } + + public boolean add(int index, E element) { + + checkPositionIndex(index); + + if (index == 0) { + addFirst(element); + return true; + } + + Node p = node(index - 1); + Node node = new Node<>(element); + node.next = p.next; + p.next = node; + size++; + return true; + } + + public void removeFirst() { + first = first.next; + size--; + } + + public void removeLast() { + Node p = first; + while (p.next.next != null) { + p = p.next; + } + p.next = null; + size--; + } + + public boolean remove(int index) { + + checkElementIndex(index); + + if (index == 0) { + removeFirst(); + } + + int pos = 0; + Node p = first; + while (pos < index - 1) { + p = p.next; + pos++; + } + p.next = p.next.next; + size--; + return true; + } + + public boolean remove(E e) { + if (e == null) { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element == null) { + p.next = x.next; + size--; + return true; + } + p = p.next; + } + } else { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element.equals(e)) { + p.next = x.next; + size--; + return true; + } + p = p.next; + } + } + return false; + } + + public boolean removeAll(E e) { + + if (first.next == null) { + return false; + } + + if (e == null) { + Node p = first; + while (p.next != null) { + Node x = p.next; + if (x.element == null) { + p.next = x.next; + size--; + } + p = p.next; + } + } else { + Node p = first; + while (p != null && p.next != null) { + if (p.next.element.equals(e)) { + p.next = p.next.next; + size--; + } else { + p = p.next; + } + } + } + return true; + } + + public void clear() { + first.next = null; + size = 0; + } + + private void checkElementIndex(int index) { + if (index >= 0 && index < size) { return; } + throw new RuntimeException("超出边界!"); + } + + private void checkPositionIndex(int index) { + if (index >= 0 && index <= size) { return; } + throw new RuntimeException("超出边界!"); + } + + public void printAll() { + Node p = first.next; + while (p != null) { + System.out.print(p.element + " "); + p = p.next; + } + System.out.println(); + } + + public List toList() { + List list = new ArrayList<>(); + Node node = first.next; + while (node != null) { + list.add(node.element); + node = node.next; + } + return list; + } + + @Getter + @Setter + private static class Node { + + private E element; + private Node next; + + public Node(E element) { + this.element = element; + this.next = null; + } + + public Node(E element, Node next) { + this.element = element; + this.next = next; + } + + } + + public static void main(String[] args) { + + Integer[] nums = { 1, 2, 3, 4, 5 }; + SinglyLinkedList list = new SinglyLinkedList<>(nums); + SinglyLinkedList reverseList = new SinglyLinkedList<>(); + for (int num : nums) { + reverseList.addFirst(num); + } + + list.add(5, 999); + System.out.println("【队尾写入链表】"); + list.printAll(); + System.out.println("【队头写入链表】"); + reverseList.printAll(); + System.out.println("999 在队列中的位置:" + list.indexOf(999)); + System.out.println("队列中位置 5 的元素值:" + list.get(5)); + + System.out.println("【删除指定位置元素】"); + list.removeLast(); + list.printAll(); + list.remove(new Integer(999)); + list.printAll(); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\215\225\351\223\276\350\241\250\347\244\272\344\276\213.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\215\225\351\223\276\350\241\250\347\244\272\344\276\213.java" new file mode 100644 index 0000000..b3ba9ec --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\215\225\351\223\276\350\241\250\347\244\272\344\276\213.java" @@ -0,0 +1,185 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Zhang Peng + * @since 2020-01-26 + */ +public class 单链表示例 { + + /** 头节点 */ + private ListNode head; + + public 单链表示例() { + this.head = new ListNode<>(null, null); + } + + public 单链表示例(ListNode head) { + this.head = head; + } + + /** + * 头插法 + * + * @param value 数据值 + */ + public void addFirst(E value) { + ListNode newNode = new ListNode<>(value, null); + newNode.next = this.head.next; + this.head.next = newNode; + } + + /** + * 尾插法 + * + * @param value 数据值 + */ + public void addLast(E value) { + // init new node + ListNode newNode = new ListNode<>(value, null); + + // find the last node + ListNode node = this.head; + while (node.next != null) { + node = node.next; + } + + // add new node to tail + node.next = newNode; + } + + public void remove(ListNode node) { + ListNode prev = this.head; + while (prev.next != null) { + ListNode curr = prev.next; + if (curr == node) { + prev.next = curr.next; + } + prev = prev.next; + } + } + + /** + * 删除首个值为 value 的节点 + * + * @param value 数据值 + * @return {@link ListNode} + */ + public E removeFirst(E value) { + ListNode prev = this.head; + while (prev.next != null) { + ListNode curr = prev.next; + if (curr.value.equals(value)) { + prev.next = curr.next; + return curr.value; + } + prev = prev.next; + } + return null; + } + + /** + * 删除所有值为 value 的节点 + *

+ * 示例: + *

+     * 输入: 1->2->6->3->4->5->6, val = 6
+     * 输出: 1->2->3->4->5
+     * 
+ * + * @see 移除链表元素 + */ + public void removeAll(E val) { + if (head.next == null) { + return; + } + + ListNode root = head; + ListNode prev = root; + while (prev != null && prev.next != null) { + if (prev.next.value.equals(val)) { + prev.next = prev.next.next; + } else { + prev = prev.next; + } + } + + head = root; + } + + /** + * 清空除头节点以外的所有节点 + */ + public void clear() { + if (head != null) { + head.next = null; + } + } + + /** + * find element + *

+ * 从头开始查找,一旦发现有数值与查找值相等的节点,直接返回此节点。如果遍历结束,表明未找到节点,返回 null。 + * + * @param value 数据值 + * @return {@link ListNode} + */ + public ListNode find(E value) { + ListNode node = this.head.next; + while (node != null) { + if (node.value.equals(value)) { + return node; + } + node = node.next; + } + return null; + } + + /** + * 反转链表 算法实现 + * + * @see 反转链表 + */ + public ListNode reverse() { + if (head == null) { + return null; + } + + ListNode prev = null; + ListNode curr = head; + while (curr != null) { + ListNode temp = curr.next; + curr.next = prev; + prev = curr; + curr = temp; + } + return prev; + } + + public List toList() { + List list = new ArrayList<>(); + ListNode node = head.next; + while (node != null) { + list.add(node.value); + node = node.next; + } + return list; + } + + private static class ListNode { + + E value; + + /** point to next node */ + ListNode next; + + public ListNode(E value, ListNode next) { + this.value = value; + this.next = next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\217\214\351\223\276\350\241\250\347\244\272\344\276\213.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\217\214\351\223\276\350\241\250\347\244\272\344\276\213.java" new file mode 100644 index 0000000..f6db8af --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/demo/\345\217\214\351\223\276\350\241\250\347\244\272\344\276\213.java" @@ -0,0 +1,132 @@ +package io.github.dunwu.algorithm.linkedlist.demo; + +/** + * @author Zhang Peng + * @since 2020-01-26 + */ +public class 双链表示例 { + + /** 头节点 */ + private DListNode head; + + /** 尾节点 */ + private DListNode tail; + + public 双链表示例() { + this.head = new DListNode<>(); + this.tail = new DListNode<>(); + + this.head.value = null; + this.head.prev = null; + this.head.next = this.tail; + + this.tail.value = null; + this.tail.prev = this.head; + this.tail.next = null; + } + + private static class DListNode { + + E value; + + DListNode prev; + + DListNode next; + + DListNode() {} + + DListNode(DListNode prev, E value, DListNode next) { + this.value = value; + this.next = next; + this.prev = prev; + } + + } + + /** + * 头插法 + * + * @param value 数据值 + */ + public void addHead(E value) { + DListNode newNode = new DListNode<>(null, value, null); + + this.head.next.prev = newNode; + newNode.next = this.head.next; + + this.head.next = newNode; + newNode.prev = this.head; + } + + /** + * 尾插法 + * + * @param value 数据值 + */ + public void addTail(E value) { + DListNode newNode = new DListNode<>(null, value, null); + + this.tail.prev.next = newNode; + newNode.prev = this.tail.prev; + + this.tail.prev = newNode; + newNode.next = this.tail; + } + + /** + * 删除节点 + * + * @param value 数据值 + */ + public void remove(E value) { + DListNode prev = this.head; + while (prev.next != this.tail) { + DListNode curr = prev.next; + if (curr.value.equals(value)) { + prev.next = curr.next; + curr.next.prev = prev; + curr.next = null; + curr.prev = null; + break; + } + prev = prev.next; + } + } + + /** + * 查找节点 + * + * @param value 数据值 + */ + public DListNode find(E value) { + DListNode node = this.head.next; + while (node != this.tail) { + if (node.value.equals(value)) { + return node; + } + node = node.next; + } + return null; + } + + public void printList() { + DListNode node = this.head.next; + while (node != this.tail) { + System.out.println(node.value); + node = node.next; + } + } + + public static void main(String[] args) { + 双链表示例 list = new 双链表示例<>(); + list.addTail(2); + list.addTail(3); + list.addHead(1); + + list.remove(1); + System.out.println("list.find(1) = " + list.find(1)); + System.out.println("list.find(2) = " + list.find(2)); + list.printList(); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/divide/\346\216\222\345\272\217\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/divide/\346\216\222\345\272\217\351\223\276\350\241\250.java" new file mode 100644 index 0000000..9c6a30e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/divide/\346\216\222\345\272\217\351\223\276\350\241\250.java" @@ -0,0 +1,72 @@ +package io.github.dunwu.algorithm.linkedlist.divide; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 148.排序链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 排序链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(1, 2, 3, 4), s.sortList(ListNode.buildList(4, 2, 1, 3))); + Assertions.assertEquals(ListNode.buildList(-1, 0, 3, 4, 5), s.sortList(ListNode.buildList(-1, 5, 3, 4, 0))); + Assertions.assertEquals(ListNode.buildList(), s.sortList(ListNode.buildList())); + } + + public static class Solution { + + public ListNode sortList(ListNode head) { + // 如果链表为空或者只有一个节点,无需排序 + if (head == null || head.next == null) { + return head; + } + // 找到中间节点 head2,并断开 head2 与其前一个节点的连接 + // 比如 head=[4,2,1,3],那么 middleNode 调用结束后 head=[4,2] head2=[1,3] + ListNode mid = middleNode(head); + // 分治 + head = sortList(head); + mid = sortList(mid); + // 合并 + return mergeTwoLists(head, mid); + } + + // 876. 链表的中间结点(快慢指针) + private ListNode middleNode(ListNode head) { + ListNode pre = head; + ListNode slow = head; + ListNode fast = head; + while (fast != null && fast.next != null) { + pre = slow; // 记录 slow 的前一个节点 + slow = slow.next; + fast = fast.next.next; + } + pre.next = null; // 断开 slow 的前一个节点和 slow 的连接 + return slow; + } + + // 21. 合并两个有序链表(双指针) + private ListNode mergeTwoLists(ListNode list1, ListNode list2) { + ListNode dummy = new ListNode(-1); // 用哨兵节点简化代码逻辑 + ListNode cur = dummy; // cur 指向新链表的末尾 + while (list1 != null && list2 != null) { + if (list1.val < list2.val) { + cur.next = list1; // 把 list1 加到新链表中 + list1 = list1.next; + } else { // 注:相等的情况加哪个节点都是可以的 + cur.next = list2; // 把 list2 加到新链表中 + list2 = list2.next; + } + cur = cur.next; + } + cur.next = list1 != null ? list1 : list2; // 拼接剩余链表 + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/palindrome/\345\233\236\346\226\207\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/palindrome/\345\233\236\346\226\207\351\223\276\350\241\250.java" new file mode 100644 index 0000000..dd4a500 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/palindrome/\345\233\236\346\226\207\351\223\276\350\241\250.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.linkedlist.palindrome; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 234. 回文链表 + * 面试题 02.06. 回文链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 回文链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode input = ListNode.buildList(1, 2, 2, 1); + Assertions.assertTrue(s.isPalindrome(input)); + ListNode input2 = ListNode.buildList(1, 2); + Assertions.assertFalse(s.isPalindrome(input2)); + } + + static class Solution { + + public boolean isPalindrome(ListNode head) { + List list = new ArrayList<>(); + ListNode p = head; + while (p != null) { + list.add(p.val); + p = p.next; + } + + int left = 0, right = list.size() - 1; + while (left < right) { + if (!list.get(left).equals(list.get(right))) { + return false; + } + left++; + right--; + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/K\344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/K\344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.java" new file mode 100644 index 0000000..18143c9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/K\344\270\252\344\270\200\347\273\204\347\277\273\350\275\254\351\223\276\350\241\250.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.linkedlist.reverse; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 25. K 个一组翻转链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class K个一组翻转链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode output = s.reverseKGroup(ListNode.buildList(1, 2, 3, 4, 5), 2); + Assertions.assertEquals(ListNode.buildList(2, 1, 4, 3, 5), output); + ListNode output2 = s.reverseKGroup(ListNode.buildList(1, 2, 3, 4, 5), 3); + Assertions.assertEquals(ListNode.buildList(3, 2, 1, 4, 5), output2); + } + + static class Solution { + + public ListNode reverseKGroup(ListNode head, int k) { + if (head == null || head.next == null) { return head; } + ListNode p = head; + for (int i = 0; i < k; i++) { + if (p == null) { return head; } + p = p.next; + } + ListNode newHead = reverseN(head, k); + head.next = reverseKGroup(p, k); + return newHead; + } + + public ListNode reverseN(ListNode head, int len) { + if (head == null) { return null; } + ListNode pre = null, cur = head; + for (int i = 0; i < len; i++) { + if (cur == null) { break; } + ListNode next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + head.next = cur; + return pre; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\250.java" new file mode 100644 index 0000000..ac54c4c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\250.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.linkedlist.reverse; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 206. 反转链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 反转链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode result = s.reverseList(ListNode.buildList(1, 2, 3, 4)); + Assertions.assertEquals(ListNode.buildList(4, 3, 2, 1), result); + + ListNode result2 = s.reverseList(ListNode.buildList(1, 2)); + Assertions.assertEquals(ListNode.buildList(2, 1), result2); + + ListNode result3 = s.reverseList(ListNode.buildList()); + Assertions.assertEquals(ListNode.buildList(), result3); + } + + static class Solution { + + public ListNode reverseList(ListNode head) { + if (head == null || head.next == null) { return head; } + ListNode pre = null, cur = head; + while (cur != null) { + ListNode next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + return pre; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\2502.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\2502.java" new file mode 100644 index 0000000..74aca5d --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\345\217\215\350\275\254\351\223\276\350\241\2502.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.linkedlist.reverse; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 92. 反转链表 II + * + * @author Zhang Peng + * @date 2025-01-20 + */ +public class 反转链表2 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode result = s.reverseBetween(ListNode.buildList(1, 2, 3, 4, 5), 2, 4); + Assertions.assertEquals(ListNode.buildList(1, 4, 3, 2, 5), result); + ListNode result2 = s.reverseBetween(ListNode.buildList(3, 5), 1, 2); + Assertions.assertEquals(ListNode.buildList(5, 3), result2); + } + + static class Solution { + + public ListNode reverseBetween(ListNode head, int m, int n) { + if (m == 1) { + return reverseN(head, n); + } + // 找到第 m 个节点的前驱 + ListNode pre = head; + for (int i = 1; i < m - 1; i++) { + pre = pre.next; + } + // 从第 m 个节点开始反转 + pre.next = reverseN(pre.next, n - m + 1); + return head; + } + + ListNode reverseN(ListNode head, int n) { + if (head == null || head.next == null) { + return head; + } + ListNode pre, cur, nxt; + pre = null; + cur = head; + nxt = head.next; + while (n > 0) { + cur.next = pre; + pre = cur; + cur = nxt; + if (nxt != null) { + nxt = nxt.next; + } + n--; + } + // 此时的 cur 是第 n + 1 个节点,head 是反转后的尾结点 + head.next = cur; + + // 此时的 pre 是反转后的头结点 + return pre; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\346\227\213\350\275\254\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\346\227\213\350\275\254\351\223\276\350\241\250.java" new file mode 100644 index 0000000..a27b82a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/reverse/\346\227\213\350\275\254\351\223\276\350\241\250.java" @@ -0,0 +1,82 @@ +package io.github.dunwu.algorithm.linkedlist.reverse; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 61. 旋转链表 + * + * @author Zhang Peng + * @since 2025-11-20 + */ +public class 旋转链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + + ListNode input = ListNode.buildList(1, 2, 3, 4, 5); + ListNode output = s.rotateRight(input, 2); + Assertions.assertEquals(ListNode.buildList(4, 5, 1, 2, 3), output); + + ListNode input2 = ListNode.buildList(0, 1, 2); + ListNode output2 = s.rotateRight(input2, 4); + Assertions.assertEquals(ListNode.buildList(2, 0, 1), output2); + + ListNode input3 = ListNode.buildList(1, 2); + ListNode output3 = s.rotateRight(input3, 1); + Assertions.assertEquals(ListNode.buildList(2, 1), output3); + + ListNode input4 = ListNode.buildList(1, 2); + ListNode output4 = s.rotateRight(input4, 3); + Assertions.assertEquals(ListNode.buildList(2, 1), output4); + } + + static class Solution { + + public ListNode rotateRight(ListNode head, int k) { + if (head == null || head.next == null) { + return head; + } + + ListNode dummy = new ListNode(-1); + dummy.next = head; + + ListNode newLast = lastFromEnd(head, k + 1); + ListNode last = newLast; + while (last.next != null) { + last = last.next; + } + + last.next = head; + dummy.next = newLast.next; + newLast.next = null; + + return dummy.next; + } + + public ListNode lastFromEnd(ListNode head, int k) { + + if (head == null || head.next == null) { + return null; + } + + int i = 0; + ListNode slow = head, fast = head; + while (i < k) { + i++; + if (fast == null) { + fast = head; + } + fast = fast.next; + } + + // fast 先走 k 步后,slow 从 head 开始出发,当 fast 到底,slow 正好是倒数第 k 个节点 + while (fast != null) { + slow = slow.next; + fast = fast.next; + } + return slow; + } + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\240.java" new file mode 100644 index 0000000..69de0e0 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\240.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 2. 两数相加 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 两数相加 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode output1 = s.addTwoNumbers(ListNode.buildList(2, 4, 3), ListNode.buildList(5, 6, 4)); + Assertions.assertEquals(ListNode.buildList(7, 0, 8), output1); + + ListNode output2 = s.addTwoNumbers(ListNode.buildList(0), ListNode.buildList(0)); + Assertions.assertEquals(ListNode.buildList(0), output2); + + ListNode output3 = s.addTwoNumbers(ListNode.buildList(9, 9, 9, 9, 9, 9, 9), ListNode.buildList(9, 9, 9, 9)); + Assertions.assertEquals(ListNode.buildList(8, 9, 9, 9, 0, 0, 0, 1), output3); + } + + static class Solution { + + public ListNode addTwoNumbers(ListNode l1, ListNode l2) { + // 在两条链表上的指针 + ListNode p1 = l1, p2 = l2; + // 虚拟头结点(构建新链表时的常用技巧) + ListNode dummy = new ListNode(-1); + // 指针 p 负责构建新链表 + ListNode p = dummy; + // 记录进位 + int carry = 0; + // 开始执行加法,两条链表走完且没有进位时才能结束循环 + while (p1 != null || p2 != null || carry > 0) { + + int val = 0; + if (p1 != null) { + val += p1.val; + p1 = p1.next; + } + if (p2 != null) { + val += p2.val; + p2 = p2.next; + } + if (carry > 0) { + val += carry; + } + + carry = val / 10; + val = val % 10; + p.next = new ListNode(val); + p = p.next; + } + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\2402.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\2402.java" new file mode 100644 index 0000000..445b9d7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\344\270\244\346\225\260\347\233\270\345\212\2402.java" @@ -0,0 +1,79 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 445. 两数相加 II + * + * @author Zhang Peng + * @date 2025-01-21 + */ +public class 两数相加2 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode result = s.addTwoNumbers(ListNode.buildList(7, 2, 4, 3), ListNode.buildList(5, 6, 4)); + Assertions.assertEquals(ListNode.buildList(7, 8, 0, 7), result); + ListNode result2 = s.addTwoNumbers(ListNode.buildList(2, 4, 3), ListNode.buildList(5, 6, 4)); + Assertions.assertEquals(ListNode.buildList(8, 0, 7), result2); + ListNode result3 = s.addTwoNumbers(ListNode.buildList(0), ListNode.buildList(0)); + Assertions.assertEquals(ListNode.buildList(0), result3); + } + + public static class Solution { + + public ListNode addTwoNumbers(ListNode l1, ListNode l2) { + ListNode r1 = reverse(l1); + ListNode r2 = reverse(l2); + ListNode res = doAddTwoNumbers(r1, r2); + return reverse(res); + } + + public ListNode reverse(ListNode head) { + ListNode pre = null, cur = head; + while (cur != null) { + ListNode next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + return pre; + } + + public ListNode doAddTwoNumbers(ListNode l1, ListNode l2) { + // 在两条链表上的指针 + ListNode p1 = l1, p2 = l2; + // 虚拟头结点(构建新链表时的常用技巧) + ListNode dummy = new ListNode(-1); + // 指针 p 负责构建新链表 + ListNode p = dummy; + // 记录进位 + int carry = 0; + // 开始执行加法,两条链表走完且没有进位时才能结束循环 + while (p1 != null || p2 != null || carry > 0) { + + int val = 0; + if (p1 != null) { + val += p1.val; + p1 = p1.next; + } + if (p2 != null) { + val += p2.val; + p2 = p2.next; + } + if (carry > 0) { + val += carry; + } + + carry = val / 10; + val = val % 10; + p.next = new ListNode(val); + p = p.next; + } + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\206\351\232\224\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\206\351\232\224\351\223\276\350\241\250.java" new file mode 100644 index 0000000..2ab09c3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\206\351\232\224\351\223\276\350\241\250.java" @@ -0,0 +1,101 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 86. 分隔链表 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 分隔链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + Assertions.assertEquals(ListNode.buildList(1, 2, 2, 4, 3, 5), + s.partition(ListNode.buildList(1, 4, 3, 2, 5, 2), 3)); + Assertions.assertEquals(ListNode.buildList(1, 2), s.partition(ListNode.buildList(2, 1), 2)); + Assertions.assertEquals(ListNode.buildList(1, 2, 3), s.partition(ListNode.buildList(3, 1, 2), 3)); + Assertions.assertEquals(ListNode.buildList(1, 0, 4, 3, 5, 2), + s.partition(ListNode.buildList(1, 4, 3, 0, 5, 2), 2)); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(ListNode.buildList(1, 2, 2, 4, 3, 5), + s2.partition(ListNode.buildList(1, 4, 3, 2, 5, 2), 3)); + Assertions.assertEquals(ListNode.buildList(1, 2), s2.partition(ListNode.buildList(2, 1), 2)); + Assertions.assertEquals(ListNode.buildList(1, 2, 3), s2.partition(ListNode.buildList(3, 1, 2), 3)); + Assertions.assertEquals(ListNode.buildList(1, 0, 4, 3, 5, 2), + s2.partition(ListNode.buildList(1, 4, 3, 0, 5, 2), 2)); + } + + static class Solution { + + public ListNode partition(ListNode head, int x) { + + if (head == null) { return null; } + + ListNode dummy = new ListNode(-1); + dummy.next = head; + + // 找到大于等于 x 的节点的前一个节点 + ListNode l = dummy, r = dummy.next; + while (r != null) { + + while (l.next != null && l.next.val < x) { + l = l.next; + } + if (l.next == null) { + break; + } + + r = l.next; + while (r.next != null && r.next.val >= x) { + r = r.next; + } + if (r.next == null) { + break; + } + + // 替换节点 + ListNode tmp = r.next; + r.next = tmp.next; + tmp.next = l.next; + l.next = tmp; + + l = l.next; + r = r.next; + } + + return dummy.next; + } + + } + + static class Solution2 { + + public ListNode partition(ListNode head, int x) { + ListNode dummy1 = new ListNode(-1); + ListNode dummy2 = new ListNode(-1); + + ListNode d1 = dummy1, d2 = dummy2, p = head; + while (p != null) { + if (p.val < x) { + d1.next = p; + d1 = d1.next; + } else { + d2.next = p; + d2 = d2.next; + } + p = p.next; + } + d2.next = null; + d1.next = dummy2.next; + return dummy1.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.java" new file mode 100644 index 0000000..8d17469 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\240.java" @@ -0,0 +1,36 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 83. 删除排序链表中的重复元素 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 删除排序链表中的重复元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode input = ListNode.buildList(1, 1, 2); + Assertions.assertEquals(ListNode.buildList(1, 2), s.deleteDuplicates(input)); + ListNode input2 = ListNode.buildList(1, 1, 2, 3, 3); + Assertions.assertEquals(ListNode.buildList(1, 2, 3), s.deleteDuplicates(input2)); + } + + static class Solution { + + public ListNode deleteDuplicates(ListNode head) { + ListNode pre = head, cur = head.next; + while (cur != null) { + pre.next = cur.next; + pre = cur; + cur = cur.next; + } + return head; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\2402.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\2402.java" new file mode 100644 index 0000000..94cc3af --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\346\216\222\345\272\217\351\223\276\350\241\250\344\270\255\347\232\204\351\207\215\345\244\215\345\205\203\347\264\2402.java" @@ -0,0 +1,62 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 82. 删除排序链表中的重复元素 II + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 删除排序链表中的重复元素2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode input = ListNode.buildList(1, 2, 3, 3, 4, 4, 5); + Assertions.assertEquals(ListNode.buildList(1, 2, 5), s.deleteDuplicates(input)); + + ListNode input2 = ListNode.buildList(1, 1, 1, 2, 3); + Assertions.assertEquals(ListNode.buildList(2, 3), s.deleteDuplicates(input2)); + + ListNode input3 = ListNode.buildList(1, 2, 2); + Assertions.assertEquals(ListNode.buildList(1), s.deleteDuplicates(input3)); + } + + public static class Solution { + + public ListNode deleteDuplicates(ListNode head) { + // 将原链表分解为两条链表 + // 一条链表存放不重复的节点,另一条链表存放重复的节点 + // 运用虚拟头结点技巧,题目说了 node.val <= 100,所以用 101 作为虚拟头结点 + ListNode dummyUniq = new ListNode(101); + ListNode dummyDup = new ListNode(101); + + ListNode pUniq = dummyUniq, pDup = dummyDup; + ListNode p = head; + + while (p != null) { + if ((p.next != null && p.val == p.next.val) || p.val == pDup.val) { + // 发现重复节点,接到重复链表后面 + pDup.next = p; + pDup = pDup.next; + } else { + // 不是重复节点,接到不重复链表后面 + pUniq.next = p; + pUniq = pUniq.next; + } + + p = p.next; + // 将原链表和新链表断开 + pUniq.next = null; + pDup.next = null; + } + + return dummyUniq.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254N\344\270\252\347\273\223\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254N\344\270\252\347\273\223\347\202\271.java" new file mode 100644 index 0000000..d3e6e42 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254N\344\270\252\347\273\223\347\202\271.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 19. 删除链表的倒数第 N 个结点 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 删除链表的倒数第N个结点 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode input1 = ListNode.buildList(1, 2, 3, 4, 5); + ListNode output1 = s.removeNthFromEnd(input1, 2); + Assertions.assertEquals(ListNode.buildList(1, 2, 3, 5), output1); + + ListNode input2 = ListNode.buildList(1); + ListNode output2 = s.removeNthFromEnd(input2, 1); + Assertions.assertEquals(ListNode.buildList(), output2); + + ListNode input3 = ListNode.buildList(1, 2); + ListNode output3 = s.removeNthFromEnd(input3, 1); + Assertions.assertEquals(ListNode.buildList(1), output3); + } + + static class Solution { + + public ListNode removeNthFromEnd(ListNode head, int n) { + ListNode dummy = new ListNode(-1); + dummy.next = head; + ListNode node = findFromEnd(dummy, n + 1); + node.next = node.next.next; + return dummy.next; + } + + public ListNode findFromEnd(ListNode head, int k) { + ListNode p1 = head; + // p1 先走 k 步 + for (int i = 0; i < k; i++) { + p1 = p1.next; + } + ListNode p2 = head; + // p1 和 p2 同时走 n - k 步 + while (p1 != null) { + p2 = p2.next; + p1 = p1.next; + } + // p2 现在指向第 n - k + 1 个节点,即倒数第 k 个节点 + return p2; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250.java" new file mode 100644 index 0000000..419f5e2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 23. 合并 K 个升序链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 合并K个升序链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + ListNode head1 = ListNode.buildList(1, 4, 5); + ListNode head2 = ListNode.buildList(1, 3, 4); + ListNode head3 = ListNode.buildList(2, 6); + ListNode result = s.mergeKLists(new ListNode[] { head1, head2, head3 }); + Assertions.assertEquals(ListNode.buildList(1, 1, 2, 3, 4, 4, 5, 6), result); + + ListNode[] array2 = new ListNode[] {}; + ListNode result2 = s.mergeKLists(array2); + Assertions.assertEquals(ListNode.buildList(), result2); + } + + static class Solution { + + public ListNode mergeKLists(ListNode[] lists) { + if (lists == null || lists.length == 0) return null; + ListNode l1 = lists[0]; + for (int i = 1; i < lists.length; i++) { + ListNode l2 = lists[i]; + l1 = mergeTwoLists(l1, l2); + } + return l1; + } + + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + // 虚拟头结点 + ListNode dummy = new ListNode(-1), p = dummy; + ListNode p1 = l1, p2 = l2; + + while (p1 != null && p2 != null) { + // 比较 p1 和 p2 两个指针 + // 将值较小的的节点接到 p 指针 + if (p1.val > p2.val) { + p.next = p2; + p2 = p2.next; + } else { + p.next = p1; + p1 = p1.next; + } + // p 指针不断前进 + p = p.next; + } + + if (p1 != null) { p.next = p1; } + if (p2 != null) { p.next = p2; } + + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.java" new file mode 100644 index 0000000..8da3bd1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250.java" @@ -0,0 +1,51 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 21. 合并两个有序链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 合并两个有序链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode h1 = ListNode.buildList(1, 2, 4); + ListNode h2 = ListNode.buildList(1, 3, 4); + ListNode result = s.mergeTwoLists(h1, h2); + Assertions.assertEquals(ListNode.buildList(1, 1, 2, 3, 4, 4), result); + } + + static class Solution { + + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + // 虚拟头结点 + ListNode dummy = new ListNode(-1), p = dummy; + ListNode p1 = l1, p2 = l2; + + while (p1 != null && p2 != null) { + // 比较 p1 和 p2 两个指针 + // 将值较小的的节点接到 p 指针 + if (p1.val > p2.val) { + p.next = p2; + p2 = p2.next; + } else { + p.next = p1; + p1 = p1.next; + } + // p 指针不断前进 + p = p.next; + } + + if (p1 != null) { p.next = p1; } + if (p2 != null) { p.next = p2; } + + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\245\207\345\201\266\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\245\207\345\201\266\351\223\276\350\241\250.java" new file mode 100644 index 0000000..5d3c5be --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\345\245\207\345\201\266\351\223\276\350\241\250.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 328. 奇偶链表 + * + * @author Zhang Peng + * @since 2020-07-08 + */ +public class 奇偶链表 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(1, 3, 5, 2, 4), s.oddEvenList(ListNode.buildList(1, 2, 3, 4, 5))); + Assertions.assertEquals(ListNode.buildList(2, 3, 6, 7, 1, 5, 4), + s.oddEvenList(ListNode.buildList(2, 1, 3, 5, 6, 4, 7))); + } + + static class Solution { + + public ListNode oddEvenList(ListNode head) { + ListNode oddDummy = new ListNode(-1); + ListNode evenDummy = new ListNode(-1); + ListNode p = head, o = oddDummy, e = evenDummy; + int i = 1; + while (p != null) { + if (i % 2 == 0) { + e.next = p; + e = e.next; + } else { + o.next = p; + o = o.next; + } + p = p.next; + i++; + } + e.next = null; + o.next = evenDummy.next; + return oddDummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\250.java" new file mode 100644 index 0000000..f73021e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\250.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 141. 环形链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 环形链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + ListNode head = ListNode.buildList(3, 2, 0, -4); + Assertions.assertFalse(s.hasCycle(head)); + + ListNode head2 = ListNode.buildCycleList(1, new int[] { 3, 2, 0, -4 }); + Assertions.assertTrue(s.hasCycle(head2)); + + ListNode head3 = ListNode.buildCycleList(0, new int[] { 1, 2 }); + Assertions.assertTrue(s.hasCycle(head3)); + + ListNode head4 = ListNode.buildCycleList(1, new int[] { 1 }); + Assertions.assertFalse(s.hasCycle(head4)); + } + + static class Solution { + + public boolean hasCycle(ListNode head) { + // 快慢指针初始化指向 head + ListNode slow = head, fast = head; + // 快指针走到末尾时停止 + while (fast != null && fast.next != null) { + // 慢指针走一步,快指针走两步 + slow = slow.next; + fast = fast.next.next; + // 快慢指针相遇,说明含有环 + if (slow == fast) { + return true; + } + } + return false; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\2502.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\2502.java" new file mode 100644 index 0000000..1c6abd9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\216\257\345\275\242\351\223\276\350\241\2502.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 142. 环形链表 II + * + * @author Zhang Peng + * @since 2020-07-08 + */ +public class 环形链表2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode input = ListNode.buildList(3, 2, 0, -4); + Assertions.assertNull(s.detectCycle(input)); + + ListNode input2 = ListNode.buildList(1); + Assertions.assertNull(s.detectCycle(input2)); + + ListNode input3 = ListNode.buildCycleList(1, new int[] { 3, 2, 0, -4 }); + Assertions.assertEquals(2, s.detectCycle(input3).val); + + ListNode input4 = ListNode.buildCycleList(0, new int[] { 1, 2 }); + Assertions.assertEquals(1, s.detectCycle(input4).val); + } + + static class Solution { + + public ListNode detectCycle(ListNode head) { + ListNode fast, slow; + fast = slow = head; + while (fast != null && fast.next != null) { + fast = fast.next.next; + slow = slow.next; + if (fast == slow) break; + } + + // fast 遇到空指针说明没有环 + if (fast == null || fast.next == null) { + return null; + } + + // 重新指向头结点 + slow = head; + + // 快慢指针同步前进,相交点就是环起点 + while (slow != fast) { + fast = fast.next; + slow = slow.next; + } + return slow; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\233\270\344\272\244\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\233\270\344\272\244\351\223\276\350\241\250.java" new file mode 100644 index 0000000..e222dfe --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\233\270\344\272\244\351\223\276\350\241\250.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 相交链表 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 相交链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + ListNode listA = ListNode.buildList(4, 1, 8, 4, 5); + ListNode listB = ListNode.buildList(5, 6, 1, 8, 4, 5); + ListNode.buildMetPot(listA, listB, 2, 3); + ListNode result = s.getIntersectionNode(listA, listB); + Assertions.assertEquals(8, result.val); + + ListNode listA2 = ListNode.buildList(1, 9, 1, 2, 4); + ListNode listB2 = ListNode.buildList(3, 2, 4); + ListNode.buildMetPot(listA2, listB2, 3, 1); + ListNode result2 = s.getIntersectionNode(listA2, listB2); + Assertions.assertEquals(2, result2.val); + + ListNode listA3 = ListNode.buildList(2, 6, 4); + ListNode listB3 = ListNode.buildList(1, 5); + ListNode result3 = s.getIntersectionNode(listA3, listB3); + Assertions.assertNull(result3); + } + + static class Solution { + + public ListNode getIntersectionNode(ListNode headA, ListNode headB) { + // pA 指向 A 链表头结点,pB 指向 B 链表头结点 + ListNode pA = headA, pB = headB; + while (pA != pB) { + // pA 走一步,如果走到 A 链表末尾,转到 B 链表 + if (pA == null) { + pA = headB; + } else { + pA = pA.next; + } + // pB 走一步,如果走到 B 链表末尾,转到 A 链表 + if (pB == null) { + pB = headA; + } else { + pB = pB.next; + } + } + return pA; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\207\215\345\244\215\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\207\215\345\244\215\350\212\202\347\202\271.java" new file mode 100644 index 0000000..0bba21c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\207\215\345\244\215\350\212\202\347\202\271.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.Set; + +/** + * 面试题 02.01. 移除重复节点 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 移除重复节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(1, 2, 3), + s.removeDuplicateNodes(ListNode.buildList(1, 2, 3, 3, 2, 1))); + Assertions.assertEquals(ListNode.buildList(1, 2), + s.removeDuplicateNodes(ListNode.buildList(1, 1, 1, 1, 2))); + } + + static class Solution { + + public ListNode removeDuplicateNodes(ListNode head) { + Set set = new HashSet<>(); + ListNode dummy = new ListNode(-1); + ListNode p = head, n = dummy; + while (p != null) { + if (!set.contains(p.val)) { + n.next = p; + n = n.next; + set.add(p.val); + } + p = p.next; + } + n.next = null; + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.java" new file mode 100644 index 0000000..6cb6000 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 203. 移除链表元素 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 移除链表元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(ListNode.buildList(1, 2, 3, 4, 5), + s.removeElements(ListNode.buildList(1, 2, 6, 3, 4, 5, 6), 6)); + Assertions.assertEquals(ListNode.buildList(), s.removeElements(ListNode.buildList(), 1)); + Assertions.assertEquals(ListNode.buildList(), s.removeElements(ListNode.buildList(7, 7, 7, 7, 7), 7)); + } + + static class Solution { + + public ListNode removeElements(ListNode head, int val) { + ListNode dummy = new ListNode(0); + ListNode p = head, q = dummy; + while (p != null) { + if (p.val != val) { + q.next = p; + q = q.next; + } + p = p.next; + } + q.next = null; + return dummy.next; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\350\277\224\345\233\236\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\350\277\224\345\233\236\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.java" new file mode 100644 index 0000000..808ba2b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\350\277\224\345\233\236\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271.java" @@ -0,0 +1,37 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 面试题 02. 返回倒数第 k 个节点 + * LCR 140. 训练计划 II + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 返回倒数第k个节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.kthToLast(ListNode.buildList(1, 2, 3, 4, 5), 2)); + Assertions.assertEquals(1, s.kthToLast(ListNode.buildList(1), 1)); + } + + static class Solution { + + public int kthToLast(ListNode head, int k) { + ListNode slow = head, fast = head; + for (int i = 0; i < k && fast != null; i++) { + fast = fast.next; + } + while (fast != null) { + fast = fast.next; + slow = slow.next; + } + return slow == null ? -1 : slow.val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.java" new file mode 100644 index 0000000..f93a67c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/linkedlist/two_pointer/\351\223\276\350\241\250\347\232\204\344\270\255\351\227\264\347\273\223\347\202\271.java" @@ -0,0 +1,35 @@ +package io.github.dunwu.algorithm.linkedlist.two_pointer; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +/** + * 876. 链表的中间结点 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 链表的中间结点 { + + public static void main(String[] args) { + Solution s = new Solution(); + ListNode input = ListNode.buildList(1, 2, 3, 4, 5); + Assertions.assertEquals(ListNode.buildList(3, 4, 5), s.middleNode(input)); + ListNode input2 = ListNode.buildList(1, 2, 3, 4, 5, 6); + Assertions.assertEquals(ListNode.buildList(4, 5, 6), s.middleNode(input2)); + } + + static class Solution { + + public ListNode middleNode(ListNode head) { + ListNode slow = head, fast = head; + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + } + return slow; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\344\270\221\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\344\270\221\346\225\260.java" new file mode 100644 index 0000000..dace497 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\344\270\221\346\225\260.java" @@ -0,0 +1,32 @@ +package io.github.dunwu.algorithm.math; + +import org.junit.jupiter.api.Assertions; + +/** + * 263. 丑数 + * + * @author Zhang Peng + * @date 2025-01-24 + */ +public class 丑数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isUgly(6)); + Assertions.assertTrue(s.isUgly(1)); + Assertions.assertFalse(s.isUgly(14)); + } + + static class Solution { + + public boolean isUgly(int n) { + if (n <= 0) { return false; } + while (n % 2 == 0) { n /= 2; } + while (n % 3 == 0) { n /= 3; } + while (n % 5 == 0) { n /= 5; } + return n == 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\345\212\240\344\270\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\345\212\240\344\270\200.java" new file mode 100644 index 0000000..334b604 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/math/\345\212\240\344\270\200.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.math; + +import org.junit.jupiter.api.Assertions; + +/** + * 66. 加一 + * + * @author Zhang Peng + * @since 2018-11-04 + */ +public class 加一 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 1, 2, 4 }, s.plusOne(new int[] { 1, 2, 3 })); + Assertions.assertArrayEquals(new int[] { 4, 3, 2, 2 }, s.plusOne(new int[] { 4, 3, 2, 1 })); + Assertions.assertArrayEquals(new int[] { 1, 0, 0, 0, 0 }, s.plusOne(new int[] { 9, 9, 9, 9 })); + } + + static class Solution { + + public int[] plusOne(int[] digits) { + for (int i = digits.length - 1; i >= 0; i--) { + if (digits[i] == 9) { + digits[i] = 0; + } else { + digits[i]++; + return digits; + } + } + int[] res = new int[digits.length + 1]; + res[0] = 1; + return res; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/GenericQueue.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/GenericQueue.java new file mode 100644 index 0000000..f75c96b --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/GenericQueue.java @@ -0,0 +1,63 @@ +package io.github.dunwu.algorithm.queue; + +/** + * @author Zhang Peng + * @since 2020-06-10 + */ +public class GenericQueue { + + // 队列的队首和队尾 + private Node head = null; + private Node tail = null; + + // 入队 + public void enqueue(T value) { + if (head == null) { + tail = new Node(value, null); + head = tail; + } else { + tail.next = new Node(value, null); + tail = tail.next; + } + } + + // 出队 + public T dequeue() { + if (head == null) { + return null; + } + + T val = head.data; + head = head.next; + if (head == null) { + tail = null; + } + return val; + } + + public void printAll() { + Node p = head; + while (p != null) { + System.out.print(p.data + " "); + p = p.next; + } + System.out.println(); + } + + private static class Node { + + private T data; + private Node next; + + public Node(T data, Node next) { + this.data = data; + this.next = next; + } + + public T getData() { + return data; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\345\212\250\346\200\201\346\211\251\345\256\271\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\345\212\250\346\200\201\346\211\251\345\256\271\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" new file mode 100644 index 0000000..f476f99 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\345\212\250\346\200\201\346\211\251\345\256\271\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.queue.demo; + +import java.util.Arrays; + +/** + * Created by wangzheng on 2018/10/9. + */ +public class 动态扩容数组实现的队列 { + + public static void main(String[] args) { + 动态扩容数组实现的队列 queue = new 动态扩容数组实现的队列(3); + queue.enqueue("1"); + queue.enqueue("2"); + queue.enqueue("3"); + queue.enqueue("4"); + queue.printAll(); + System.out.println("dequeue " + queue.dequeue()); + queue.printAll(); + System.out.println("dequeue " + queue.dequeue()); + queue.printAll(); + } + + // 数组:items,数组大小:n + private String[] items; + private int n = 0; + // head表示队头下标,tail表示队尾下标 + private int head = 0; + private int tail = 0; + + // 申请一个大小为capacity的数组 + public 动态扩容数组实现的队列(int capacity) { + items = new String[capacity]; + n = capacity; + } + + // 入队操作,将item放入队尾 + public boolean enqueue(String item) { + // 队列已满,扩容 + if (tail == n) { + n = n * 2; + items = Arrays.copyOf(items, n); + } + + items[tail] = item; + tail++; + return true; + } + + // 出队 + public String dequeue() { + // 如果head == tail 表示队列为空 + if (head == tail) return null; + String val = items[head]; + items = Arrays.copyOfRange(items, 1, tail); + tail--; + return val; + } + + public void printAll() { + for (int i = head; i < tail; ++i) { + System.out.print(items[i] + " "); + } + System.out.println(); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" new file mode 100644 index 0000000..0e321f7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\346\225\260\347\273\204\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" @@ -0,0 +1,61 @@ +package io.github.dunwu.algorithm.queue.demo; + +/** + * 用数组实现的队列 + * Created by wangzheng on 2018/10/9. + */ +public class 数组实现的队列 { + + public static void main(String[] args) { + 数组实现的队列 queue = new 数组实现的队列(3); + queue.enqueue("1"); + queue.enqueue("2"); + queue.enqueue("3"); + queue.enqueue("4"); + queue.printAll(); + System.out.println("dequeue " + queue.dequeue()); + queue.printAll(); + System.out.println("dequeue " + queue.dequeue()); + queue.printAll(); + } + + // 数组:items,数组大小:n + private String[] items; + private int n = 0; + // head表示队头下标,tail表示队尾下标 + private int head = 0; + private int tail = 0; + + // 申请一个大小为capacity的数组 + public 数组实现的队列(int capacity) { + items = new String[capacity]; + n = capacity; + } + + // 入队 + public boolean enqueue(String item) { + // 如果tail == n 表示队列已经满了 + if (tail == n) return false; + items[tail] = item; + ++tail; + return true; + } + + // 出队 + public String dequeue() { + // 如果head == tail 表示队列为空 + if (head == tail) return null; + // 为了让其他语言的同学看的更加明确,把--操作放到单独一行来写了 + String ret = items[head]; + ++head; + return ret; + } + + public void printAll() { + for (int i = head; i < tail; ++i) { + System.out.print(items[i] + " "); + } + System.out.println(); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" new file mode 100644 index 0000000..3117ad2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/demo/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\351\230\237\345\210\227.java" @@ -0,0 +1,77 @@ +package io.github.dunwu.algorithm.queue.demo; + +/** + * 基于链表实现的队列 + *

+ * Author: Zheng + */ +public class 链表实现的队列 { + + public static void main(String[] args) { + 链表实现的队列 queue = new 链表实现的队列(); + queue.enqueue("1"); + queue.enqueue("2"); + queue.enqueue("3"); + queue.enqueue("4"); + queue.printAll(); + System.out.println("dequeue " + queue.dequeue()); + queue.printAll(); + System.out.println("dequeue " + queue.dequeue()); + queue.printAll(); + } + + // 队列的队首和队尾 + private Node head = null; + private Node tail = null; + + // 入队 + public void enqueue(String value) { + if (head == null) { + tail = new Node(value, null); + head = tail; + } else { + tail.next = new Node(value, null); + tail = tail.next; + } + } + + // 出队 + public String dequeue() { + if (head == null) { + return null; + } + + String val = head.data; + head = head.next; + if (head == null) { + tail = null; + } + return val; + } + + public void printAll() { + Node p = head; + while (p != null) { + System.out.print(p.data + " "); + p = p.next; + } + System.out.println(); + } + + private static class Node { + + private String data; + private Node next; + + public Node(String data, Node next) { + this.data = data; + this.next = next; + } + + public String getData() { + return data; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/MonotonicQueue.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/MonotonicQueue.java new file mode 100644 index 0000000..a13cdb2 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/MonotonicQueue.java @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.queue.monotonic; + +import java.util.LinkedList; + +// 单调队列的实现,可以高效维护最大值和最小值 +class MonotonicQueue> { + + // 常规队列,存储所有元素 + LinkedList q = new LinkedList<>(); + // 元素降序排列的单调队列,头部是最大值 + LinkedList maxq = new LinkedList<>(); + // 元素升序排列的单调队列,头部是最小值 + LinkedList minq = new LinkedList<>(); + + public void push(E elem) { + // 维护常规队列,直接在队尾插入元素 + q.addLast(elem); + + // 维护 maxq,将小于 elem 的元素全部删除 + while (!maxq.isEmpty() && maxq.getLast().compareTo(elem) < 0) { + maxq.pollLast(); + } + maxq.addLast(elem); + + // 维护 minq,将大于 elem 的元素全部删除 + while (!minq.isEmpty() && minq.getLast().compareTo(elem) > 0) { + minq.pollLast(); + } + minq.addLast(elem); + } + + public E max() { + // maxq 的头部是最大元素 + return maxq.getFirst(); + } + + public E min() { + // minq 的头部是最大元素 + return minq.getFirst(); + } + + public E pop() { + // 从标准队列头部弹出需要删除的元素 + E deleteVal = q.pollFirst(); + assert deleteVal != null; + + // 由于 push 的时候会删除元素,deleteVal 可能已经被删掉了 + if (deleteVal.equals(maxq.getFirst())) { + maxq.pollFirst(); + } + if (deleteVal.equals(minq.getFirst())) { + minq.pollFirst(); + } + return deleteVal; + } + + public int size() { + // 标准队列的大小即是当前队列的大小 + return q.size(); + } + + public boolean isEmpty() { + return q.isEmpty(); + } + +} \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.java" new file mode 100644 index 0000000..3b9aaf2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.queue.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * 239. 滑动窗口最大值 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 滑动窗口最大值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 3, 3, 5, 5, 6, 7 }, + s.maxSlidingWindow(new int[] { 1, 3, -1, -3, 5, 3, 6, 7 }, 3)); + Assertions.assertArrayEquals(new int[] { 1 }, s.maxSlidingWindow(new int[] { 1 }, 1)); + } + + static class Solution { + + public int[] maxSlidingWindow(int[] nums, int k) { + MonotonicQueue window = new MonotonicQueue(); + List res = new ArrayList<>(); + + for (int i = 0; i < nums.length; i++) { + if (i < k - 1) { + window.push(nums[i]); + } else { + window.push(nums[i]); + res.add(window.max()); + window.pop(nums[i - (k - 1)]); + } + } + + int[] arr = new int[res.size()]; + for (int i = 0; i < res.size(); i++) { + arr[i] = res.get(i); + } + return arr; + } + + static class MonotonicQueue { + + LinkedList q = new LinkedList<>(); + + public void push(int n) { + // 将小于 n 的元素全部删除 + while (!q.isEmpty() && q.getLast() < n) { + q.pollLast(); + } + // 然后将 n 加入尾部 + q.addLast(n); + } + + public int max() { + return q.getFirst(); + } + + public void pop(int n) { + if (n == q.getFirst()) { + q.pollFirst(); + } + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\350\256\276\350\256\241\350\207\252\345\212\251\347\273\223\347\256\227\347\263\273\347\273\237.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\350\256\276\350\256\241\350\207\252\345\212\251\347\273\223\347\256\227\347\263\273\347\273\237.java" new file mode 100644 index 0000000..69586cd --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/monotonic/\350\256\276\350\256\241\350\207\252\345\212\251\347\273\223\347\256\227\347\263\273\347\273\237.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.queue.monotonic; + +import org.junit.jupiter.api.Assertions; + +/** + * 739. 每日温度 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 设计自助结算系统 { + + public static void main(String[] args) { + Checkout c = new Checkout(); + c.add(4); + c.add(7); + Assertions.assertEquals(7, c.get_max()); + Assertions.assertEquals(4, c.remove()); + Assertions.assertEquals(7, c.get_max()); + } + + static class Checkout { + + private MonotonicQueue q; + public Checkout() { + q = new MonotonicQueue<>(); + } + + public int get_max() { + return q.isEmpty() ? -1 : q.max(); + } + + public void add(int value) { + q.push(value); + } + + public int remove() { + if (q.isEmpty()) { return -1; } + return q.pop(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\344\271\260\347\245\250\351\234\200\350\246\201\347\232\204\346\227\266\351\227\264.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\344\271\260\347\245\250\351\234\200\350\246\201\347\232\204\346\227\266\351\227\264.java" new file mode 100644 index 0000000..e350c5c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\344\271\260\347\245\250\351\234\200\350\246\201\347\232\204\346\227\266\351\227\264.java" @@ -0,0 +1,36 @@ +package io.github.dunwu.algorithm.queue; + +import org.junit.jupiter.api.Assertions; + +/** + * 2073. 买票需要的时间 + * + * @author Zhang Peng + * @since 2025-11-26 + */ +public class 买票需要的时间 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.timeRequiredToBuy(new int[] { 2, 3, 2 }, 2)); + Assertions.assertEquals(8, s.timeRequiredToBuy(new int[] { 5, 1, 1, 1 }, 0)); + } + + static class Solution { + + public int timeRequiredToBuy(int[] tickets, int k) { + int i = 0; + int seconds = 0; + while (tickets[k] != 0) { + if (tickets[i] != 0) { + tickets[i]--; + seconds++; + } + i = (i + 1) % tickets.length; + } + return seconds; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.java" new file mode 100644 index 0000000..00e74d9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\346\234\200\350\277\221\347\232\204\350\257\267\346\261\202\346\254\241\346\225\260.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.queue; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 933. 最近的请求次数 + * + * @author Zhang Peng + * @since 2020-06-10 + */ +public class 最近的请求次数 { + + public static void main(String[] args) { + RecentCounter recentCounter = new RecentCounter(); + recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1 + recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2 + recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3 + recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3 + } + + static class RecentCounter { + + private Queue queue; + + public RecentCounter() { + queue = new LinkedList<>(); + } + + public int ping(int t) { + queue.offer(t); + while (queue.peek() < t - 3000) { + queue.poll(); + } + return queue.size(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.java" new file mode 100644 index 0000000..7628222 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\347\224\250\351\230\237\345\210\227\345\256\236\347\216\260\346\240\210.java" @@ -0,0 +1,69 @@ +package io.github.dunwu.algorithm.queue; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 225. 用队列实现栈 + * + * @author Zhang Peng + * @since 2020-01-18 + */ +public class 用队列实现栈 { + + public static void main(String[] args) { + MyStack s1 = new MyStack(); + s1.push(1); + s1.push(2); + Assertions.assertEquals(2, s1.top()); + Assertions.assertEquals(2, s1.pop()); + Assertions.assertFalse(s1.empty()); + + int max = 10; + MyStack stack = new MyStack(); + for (int i = 1; i <= max; i++) { + stack.push(i); + } + for (int i = 1; i <= max; i++) { + Assertions.assertEquals(max - i + 1, stack.top()); + Assertions.assertEquals(max - i + 1, stack.pop()); + } + } + + static class MyStack { + + private Integer top; + Queue q; + + public MyStack() { + q = new LinkedList<>(); + } + + public void push(int x) { + q.offer(x); + top = x; + } + + public int pop() { + int size = q.size(); + for (int i = 1; i < size; i++) { + Integer val = q.poll(); + q.offer(val); + top = val; + } + return q.poll(); + } + + public int top() { + return top == null ? 0 : top; + } + + public boolean empty() { + return q.isEmpty(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227.java" new file mode 100644 index 0000000..1db2d49 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\256\276\350\256\241\345\276\252\347\216\257\345\217\214\347\253\257\351\230\237\345\210\227.java" @@ -0,0 +1,145 @@ +package io.github.dunwu.algorithm.queue; + +/** + * 641. 设计循环双端队列 + * + * @author Zhang Peng + * @since 2020-06-10 + */ +public class 设计循环双端队列 { + + public static void main(String[] args) { + 设计循环双端队列 queue = new 设计循环双端队列(3); + queue.insertFront(1); + queue.insertFront(2); + queue.insertFront(3); + queue.insertFront(4); + queue.printAll(); + queue.deleteFront(); + queue.printAll(); + queue.deleteFront(); + queue.printAll(); + queue.deleteFront(); + queue.printAll(); + + queue.insertLast(1); + queue.insertLast(2); + queue.insertLast(3); + queue.insertLast(4); + queue.printAll(); + queue.deleteLast(); + queue.printAll(); + // System.out.println("rear: " + queue.Rear()); + // System.out.println("full: " + queue.isFull()); + // queue.deQueue(); + // queue.enQueue(4); + // queue.printAll(); + // System.out.println("rear: " + queue.Rear()); + } + + private int[] data; + private int head; + // head表示队头下标,tail表示队尾下标 + private int tail; + private int capacity; + + /** Initialize your data structure here. Set the size of the deque to be k. */ + public 设计循环双端队列(int k) { + this.capacity = k + 1; + this.data = new int[this.capacity]; + } + + /** Adds an item at the front of Deque. Return true if the operation is successful. */ + public boolean insertFront(int value) { + if (isFull()) { + return false; + } + + head = (head - 1 + capacity) % capacity; + data[head] = value; + return true; + } + + /** Adds an item at the rear of Deque. Return true if the operation is successful. */ + public boolean insertLast(int value) { + if (isFull()) { + return false; + } + + this.data[tail] = value; + tail = (tail + 1) % capacity; + return true; + } + + /** Deletes an item from the front of Deque. Return true if the operation is successful. */ + public boolean deleteFront() { + if (isEmpty()) { + return false; + } + + head = (head + 1) % capacity; + return true; + } + + /** Deletes an item from the rear of Deque. Return true if the operation is successful. */ + public boolean deleteLast() { + if (isEmpty()) { + return false; + } + + tail = (tail - 1 + capacity) % capacity; + return true; + } + + /** Get the front item from the deque. */ + public int getFront() { + if (isEmpty()) { + return -1; + } + + return data[head]; + } + + /** Get the last item from the deque. */ + public int getRear() { + if (isEmpty()) { + return -1; + } + + int temp = (tail - 1 + capacity) % capacity; + return data[temp]; + } + + /** Checks whether the circular deque is empty or not. */ + public boolean isEmpty() { + if (head == tail) { + return true; + } + return false; + } + + /** Checks whether the circular deque is full or not. */ + public boolean isFull() { + if ((tail + 1) % capacity == head) { + return true; + } + return false; + } + + public void printAll() { + if (head == tail) { + System.out.println("队列已空"); + return; + } + for (int i = head; i != tail; ) { + System.out.print(data[i] + "\t"); + if (i == capacity - 1) { + i = 0; + } else { + i++; + } + } + System.out.println(); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\266\205\347\272\247\344\270\221\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\266\205\347\272\247\344\270\221\346\225\260.java" new file mode 100644 index 0000000..ab5b321 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/\350\266\205\347\272\247\344\270\221\346\225\260.java" @@ -0,0 +1,73 @@ +package io.github.dunwu.algorithm.queue; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashSet; +import java.util.PriorityQueue; +import java.util.Set; + +/** + * 313. 超级丑数 + * + * @author Zhang Peng + * @date 2025-01-24 + */ +public class 超级丑数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(32, s.nthSuperUglyNumber(12, new int[] { 2, 7, 13, 19 })); + Assertions.assertEquals(1, s.nthSuperUglyNumber(1, new int[] { 2, 3, 5 })); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(32, s2.nthSuperUglyNumber(12, new int[] { 2, 7, 13, 19 })); + Assertions.assertEquals(1, s2.nthSuperUglyNumber(1, new int[] { 2, 3, 5 })); + } + + // 优先队列(堆)方案 + static class Solution { + + public int nthSuperUglyNumber(int n, int[] primes) { + Set set = new HashSet<>(); + PriorityQueue queue = new PriorityQueue<>(); + set.add(1L); + queue.add(1L); + for (int i = 1; i <= n; i++) { + long curVal = queue.poll(); + if (i == n) { return (int) curVal; } + for (int prime : primes) { + long nextVal = curVal * prime; + if (!set.contains(nextVal)) { + set.add(nextVal); + queue.add(nextVal); + } + } + } + return -1; + } + + } + + // 多路归并方案 + static class Solution2 { + + public int nthSuperUglyNumber(int n, int[] nums) { + int m = nums.length; + PriorityQueue q = new PriorityQueue<>((a, b) -> a[0] - b[0]); + for (int i = 0; i < m; i++) { + q.add(new int[] { nums[i], i, 0 }); + } + int[] dp = new int[n]; + dp[0] = 1; + for (int j = 1; j < n; ) { + int[] poll = q.poll(); + int num = poll[0], i = poll[1], idx = poll[2]; + if (num != dp[j - 1]) dp[j++] = num; + q.add(new int[] { dp[idx + 1] * nums[i], i, idx + 1 }); + } + return dp[n - 1]; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/HashDemo.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/HashDemo.java index 9233b08..30f43e1 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/HashDemo.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/HashDemo.java @@ -1,13 +1,17 @@ -package io.github.dunwu.ds.search; +package io.github.dunwu.algorithm.search; /** * 为了精简示例代码,所有参数都用 public */ @SuppressWarnings("all") class HashTable { + public int key = 0; // 关键字 + public int data = 0; // 数值 + public int count = 0; // 探查次数 + } /** @@ -18,83 +22,32 @@ class HashTable { public class HashDemo { private final static int MAXSIZE = 13; + private final static int MODULO = 13; + private final static int NULLKEY = 1; - private final static int DELKEY = 2; - private final static int SUCCESS = 0; - private final static int FAILED = 0xFFFFFFFF; - /** - * 查找哈希表 - * 构造哈希表采用除留取余法,即f(key) = key mod p (p ≤ size) - * 解决冲突采用开放定址法,即f2(key) = (f(key) + i) mod p (1 ≤ i ≤ size-1) - * ha为哈希表,p为模,size为哈希表大小,key为要查找的关键字 - */ - private int searchHashTable(HashTable[] ha, int p, int size, int key) { - // 采用除留取余法找哈希地址 - int addr = key % p; + private final static int DELKEY = 2; - // 若发生冲突,用开放定址法找下一个哈希地址 - while (ha[addr].key != NULLKEY && ha[addr].key != key) { - addr = (addr + 1) % size; - } + private final static int SUCCESS = 0; - if (ha[addr].key == key) { - // 查找成功 - return addr; - } else { - // 查找失败 - return FAILED; - } - } + private final static int FAILED = 0xFFFFFFFF; - /** - * 删除哈希表中关键字为key的记录 - * 找到要删除的记录,将关键字置为删除标记DELKEY - */ - public int deleteHashTable(HashTable[] ha, int p, int size, int key) { - int addr = 0; - addr = searchHashTable(ha, p, size, key); - if (FAILED != addr) { - // 将该位置的关键字置为DELKEY - ha[addr].key = DELKEY; - return SUCCESS; - } else { - // 查找不到记录,直接返回NULLKEY - return NULLKEY; + public static void main(String[] args) { + // int[] list = {3, 112, 245, 27, 44, 19, 76, 29, 90} + int[] list = { 1, 9, 25, 11, 12, 35, 17, 29 }; + HashTable[] ha = new HashTable[MAXSIZE]; + for (int i = 0; i < ha.length; i++) { + ha[i] = new HashTable(); } - } - /** - * 将待插入的关键字key插入哈希表 - * 先调用查找算法,若在表中找到待插入的关键字,则插入失败; - * 若在表中找到一个开放地址,则将待插入的结点插入到其中,则插入成功。 - */ - private void insertHashTable(HashTable[] ha, int p, int size, int key) { - int i = 1; - int addr = 0; - // 通过哈希函数获取哈希地址 - addr = key % p; - // 如果没有冲突,直接插入 - if (ha[addr].key == NULLKEY || ha[addr].key == DELKEY) { - ha[addr].key = key; - ha[addr].count = 1; - // 如果有冲突,使用开放定址法处理冲突 - } else { - do { - // 寻找下一个哈希地址 - addr = (addr + 1) % size; - i++; - } while (ha[addr].key != NULLKEY && ha[addr].key != DELKEY); - - ha[addr].key = key; - ha[addr].count = i; - } + HashDemo search = new HashDemo(); + search.createHashTable(ha, list, MODULO, MAXSIZE); + search.displayHashTable(ha); } /** - * 创建哈希表 - * 先将哈希表中各关键字清空,使其地址为开放的,然后调用插入算法将给定的关键字序列依次插入。 + * 创建哈希表 先将哈希表中各关键字清空,使其地址为开放的,然后调用插入算法将给定的关键字序列依次插入。 */ public void createHashTable(HashTable[] ha, int[] list, int p, int size) { int i = 0; @@ -143,18 +96,68 @@ private void displayHashTable(HashTable[] ha) { System.out.println(); } - public static void main(String[] args) { - // int[] list = {3, 112, 245, 27, 44, 19, 76, 29, 90}; - int[] list = {1, 9, 25, 11, 12, 35, 17, 29}; - HashTable[] ha = new HashTable[MAXSIZE]; - for (int i = 0; i < ha.length; i++) { - ha[i] = new HashTable(); + /** + * 将待插入的关键字key插入哈希表 先调用查找算法,若在表中找到待插入的关键字,则插入失败; 若在表中找到一个开放地址,则将待插入的结点插入到其中,则插入成功。 + */ + private void insertHashTable(HashTable[] ha, int p, int size, int key) { + int i = 1; + int addr = 0; + // 通过哈希函数获取哈希地址 + addr = key % p; + // 如果没有冲突,直接插入 + if (ha[addr].key == NULLKEY || ha[addr].key == DELKEY) { + ha[addr].key = key; + ha[addr].count = 1; + // 如果有冲突,使用开放定址法处理冲突 + } else { + do { + // 寻找下一个哈希地址 + addr = (addr + 1) % size; + i++; + } + while (ha[addr].key != NULLKEY && ha[addr].key != DELKEY); + + ha[addr].key = key; + ha[addr].count = i; } + } - HashDemo search = new HashDemo(); - search.createHashTable(ha, list, MODULO, MAXSIZE); - search.displayHashTable(ha); + /** + * 删除哈希表中关键字为key的记录 找到要删除的记录,将关键字置为删除标记DELKEY + */ + public int deleteHashTable(HashTable[] ha, int p, int size, int key) { + int addr = 0; + addr = searchHashTable(ha, p, size, key); + if (FAILED != addr) { + // 将该位置的关键字置为DELKEY + ha[addr].key = DELKEY; + return SUCCESS; + } else { + // 查找不到记录,直接返回NULLKEY + return NULLKEY; + } + } + /** + * 查找哈希表 构造哈希表采用除留取余法,即f(key) = key mod p (p ≤ size) 解决冲突采用开放定址法,即f2(key) = (f(key) + i) mod p (1 ≤ i ≤ size-1) + * ha为哈希表,p为模,size为哈希表大小,key为要查找的关键字 + */ + private int searchHashTable(HashTable[] ha, int p, int size, int key) { + // 采用除留取余法找哈希地址 + int addr = key % p; + + // 若发生冲突,用开放定址法找下一个哈希地址 + while (ha[addr].key != NULLKEY && ha[addr].key != key) { + addr = (addr + 1) % size; + } + + if (ha[addr].key == key) { + // 查找成功 + return addr; + } else { + // 查找失败 + return FAILED; + } } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/Search.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/Search.java index 02645bc..f4a8b0d 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/Search.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/Search.java @@ -1,13 +1,15 @@ -package io.github.dunwu.ds.search; +package io.github.dunwu.algorithm.search; /** * @author Zhang Peng */ public interface Search { + /** * @param array 要查找的数组 - * @param key 要查找的 key + * @param key 要查找的 key * @return 返回第一个匹配 key 值的数组元素所在位置 */ - > int find(T array[], T key); + > int find(T[] array, T key); + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/SearchStrategy.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/SearchStrategy.java index 217d4bd..31afa41 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/SearchStrategy.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/SearchStrategy.java @@ -1,29 +1,32 @@ -package io.github.dunwu.ds.search; +package io.github.dunwu.algorithm.search; -import io.github.dunwu.ds.util.ArrayUtil; +import cn.hutool.core.util.ArrayUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 使用策略模式,对算法进行包装 + * * @author Zhang Peng */ public class SearchStrategy { + + private static final Logger logger = LoggerFactory.getLogger(SearchStrategy.class); + private Search search; - private static final Logger logger = LoggerFactory.getLogger( - SearchStrategy.class); public SearchStrategy(Search search) { - this.search = search; + search = search; } public int find(Integer[] list, int key) { - logger.info(this.search.getClass().getSimpleName() + " 查找开始:"); - logger.info("要查找的线性表:{}", ArrayUtil.getArrayString(list, 0, list.length - 1)); + logger.info(search.getClass().getSimpleName() + " 查找开始:"); + logger.info("要查找的线性表:{}", ArrayUtil.toString(list)); logger.info("要查找的 key:{}", key); - int index = this.search.find(list, key); + int index = search.find(list, key); logger.info("{} 的位置是:{}", key, index); return index; } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/strategy/BinarySearch.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/strategy/BinarySearch.java index d9e56be..1a95b3a 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/strategy/BinarySearch.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/strategy/BinarySearch.java @@ -1,18 +1,10 @@ -package io.github.dunwu.ds.search.strategy; +package io.github.dunwu.algorithm.search.strategy; -import io.github.dunwu.ds.search.Search; +import io.github.dunwu.algorithm.search.Search; /** - * 二分查找又称折半查找,它是一种效率较高的查找方法。 - * 二分查找需要两个前提:(1) 必须是顺序存储结构。(2) 必须是有序的表。 - * 算法: - * 从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功; - * 若扫描结束仍没有找到等于关键字的结点,表示查找失败。 - * 算法分析: - * 最坏情况 O(log n) - * 最好情况 O(1) - * 平均情况 O(log n) - * 最坏情况下的空间复杂性 O(1) + * 二分查找又称折半查找,它是一种效率较高的查找方法。 二分查找需要两个前提:(1) 必须是顺序存储结构。(2) 必须是有序的表。 算法: 从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功; + * 若扫描结束仍没有找到等于关键字的结点,表示查找失败。 算法分析: 最坏情况 O(log n) 最好情况 O(1) 平均情况 O(log n) 最坏情况下的空间复杂性 O(1) * * @author Zhang Peng */ @@ -22,15 +14,15 @@ public class BinarySearch implements Search { * 查找方法 * * @param array 被查找的线性表 - * @param key 被查找的 key + * @param key 被查找的 key * @return 成功返回 key 的位置;失败返回 -1 */ @Override - public > int find(T array[], T key) { + public > int find(T[] array, T key) { return search(array, key, 0, array.length); } - private > int search(T array[], T key, int left, int right) { + private > int search(T[] array, T key, int left, int right) { // this means that the key not found if (right < left) { return -1; @@ -49,4 +41,5 @@ private > int search(T array[], T key, int left, int rig return mid; } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/strategy/OrderSearch.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/strategy/OrderSearch.java index 48efcf8..04d1181 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/strategy/OrderSearch.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/strategy/OrderSearch.java @@ -1,17 +1,10 @@ -package io.github.dunwu.ds.search.strategy; +package io.github.dunwu.algorithm.search.strategy; -import io.github.dunwu.ds.search.Search; +import io.github.dunwu.algorithm.search.Search; /** - * 顺序查找是一种最简单的查找算法。它的查找效率不高。 - * 算法: - * 从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功; - * 若扫描结束仍没有找到关键字等于k的结点,表示查找失败。 - * 算法分析: - * 最坏情况 O(n) - * 最好情况 O(1) - * 平均情况 O(n) - * 最坏情况下的空间复杂性 + * 顺序查找是一种最简单的查找算法。它的查找效率不高。 算法: 从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功; 若扫描结束仍没有找到关键字等于k的结点,表示查找失败。 算法分析: + * 最坏情况 O(n) 最好情况 O(1) 平均情况 O(n) 最坏情况下的空间复杂性 * * @author Zhang Peng */ @@ -21,7 +14,7 @@ public class OrderSearch implements Search { * 查找方法 * * @param array 被查找的线性表 - * @param key 被查找的 key + * @param key 被查找的 key * @return 成功返回 key 的位置;失败返回 -1 */ @Override @@ -33,4 +26,5 @@ public > int find(T[] array, T key) { } return -1; } + } diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/x\347\232\204\345\271\263\346\226\271\346\240\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/x\347\232\204\345\271\263\346\226\271\346\240\271.java" new file mode 100644 index 0000000..ebdb8a1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/x\347\232\204\345\271\263\346\226\271\346\240\271.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.search; + +import org.junit.jupiter.api.Assertions; + +import java.math.BigDecimal; + +/** + * @author Zhang Peng + * @since 2020-07-04 + */ +public class x的平方根 { + + public static void main(String[] args) { + Assertions.assertEquals(2, mySqrt(4)); + Assertions.assertEquals(3, mySqrt(9)); + Assertions.assertEquals(2, mySqrt(8)); // 8 的平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 + Assertions.assertEquals(2.8285f, mySqrt2(8, 4)); // 8 的平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 + } + + // 利用二分法实现求平发根 + public static int mySqrt(int x) { + if (x == 0 || x == 1) return x; + int l = 1, r = x, res = x; + while (l <= r) { + int m = (l + r) / 2; + if (m == x / m) { + return m; + } else if (m > x / m) { + r = m - 1; + } else { + l = m + 1; + res = m; + } + } + return res; + } + + // 利用二分法实现求浮点数平发根,支持指定精度 e (小数点位数) + public static float mySqrt2(float x, int e) { + if (x == 0 || Float.compare(x, 1f) == 0) return x; + float l = 1f, r = x, res = x; + while (l <= r) { + float m = (l + r) / 2; + if (m == x / m) { + BigDecimal decimal = new BigDecimal(m).setScale(e, BigDecimal.ROUND_UP); + return decimal.floatValue(); + } else if (m > x / m) { + r = m; + } else { + l = m; + res = m; + } + } + BigDecimal decimal = new BigDecimal(res).setScale(e, BigDecimal.ROUND_UP); + return decimal.floatValue(); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/\346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/\346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265.java" new file mode 100644 index 0000000..2cb89b6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/\346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265.java" @@ -0,0 +1,61 @@ +package io.github.dunwu.algorithm.search; + +import org.junit.jupiter.api.Assertions; + +/** + * https://leetcode-cn.com/problems/search-insert-position/ + * + * @author Zhang Peng + * @since 2020-07-13 + */ +public class 搜索二维矩阵 { + + public static void main(String[] args) { + int[][] metrix = { + { 1, 3, 5, 7 }, + { 10, 11, 16, 20 }, + { 23, 30, 34, 50 } + }; + Assertions.assertTrue(searchMatrix(metrix, 3)); + Assertions.assertFalse(searchMatrix(metrix, 13)); + // Assertions.assertEquals(1, searchMatrix(metrix, 2)); + // Assertions.assertEquals(4, searchMatrix(metrix, 7)); + } + + public static boolean searchMatrix(int[][] matrix, int target) { + int rowLen = matrix.length; + int columnLen = matrix[0].length; + + // 剪枝 + if (matrix[rowLen - 1][columnLen - 1] < target) { + return false; + } + + int rbegin = 0, rend = rowLen - 1; + while (rbegin < rend) { + int rmid = rbegin + (rend - rbegin) / 2; + if (matrix[rmid][columnLen - 1] == target) { + return true; + } else if (matrix[rmid][columnLen - 1] < target) { + rbegin = rmid + 1; + } else { + rend = rmid; + } + } + + int cbegin = 0, cend = columnLen - 1; + while (cbegin < cend) { + int cmid = cbegin + (cend - cbegin) / 2; + if (matrix[rbegin][cmid] == target) { + return true; + } else if (matrix[rbegin][cmid] < target) { + cbegin = cmid + 1; + } else { + cend = cmid; + } + } + + return false; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" new file mode 100644 index 0000000..d24c13c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/\346\220\234\347\264\242\346\217\222\345\205\245\344\275\215\347\275\256.java" @@ -0,0 +1,56 @@ +package io.github.dunwu.algorithm.search; + +import org.junit.jupiter.api.Assertions; + +/** + * https://leetcode-cn.com/problems/search-insert-position/ + * + * @author Zhang Peng + * @since 2020-07-13 + */ +public class 搜索插入位置 { + + public static void main(String[] args) { + int[] nums = { 1, 3, 5, 6 }; + Assertions.assertEquals(2, searchInsert2(nums, 5)); + Assertions.assertEquals(1, searchInsert2(nums, 2)); + Assertions.assertEquals(4, searchInsert2(nums, 7)); + } + + public static int searchInsert(int[] nums, int target) { + + int len = nums.length; + int left = 0; + int right = len - 1; + if (nums[len - 1] < target) { + return len; + } + while (left < right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else { + right = mid; + } + } + return left; + } + + public static int searchInsert2(int[] nums, int target) { + int N = nums.length; + if (target < nums[0]) return 0; + if (target > nums[N - 1]) return N; + int left = 0, right = N - 1; + + while (left < right) { + int mid = left + (right - left) / 2; + if (nums[mid] < target) { + left = mid + 1; + } else { + right = mid; + } + } + return left; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/\347\254\254\344\270\200\344\270\252\351\224\231\350\257\257\347\232\204\347\211\210\346\234\254.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/\347\254\254\344\270\200\344\270\252\351\224\231\350\257\257\347\232\204\347\211\210\346\234\254.java" new file mode 100644 index 0000000..788a232 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/search/\347\254\254\344\270\200\344\270\252\351\224\231\350\257\257\347\232\204\347\211\210\346\234\254.java" @@ -0,0 +1,30 @@ +package io.github.dunwu.algorithm.search; + +/** + * @author Zhang Peng + * @since 2020-07-13 + */ +public class 第一个错误的版本 { + + public static void main(String[] args) { + + } + + public static int firstBadVersion(int n) { + int begin = 1, end = n; + while (begin < end) { + int mid = begin + (end - begin) / 2; + if (isBadVersion(mid)) { + end = mid; + } else { + begin = mid; + } + } + return begin; + } + + public static boolean isBadVersion(int n) { + return true; + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/Sort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/Sort.java index b34270c..bfbe8ca 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/Sort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/Sort.java @@ -1,12 +1,17 @@ -package io.github.dunwu.ds.sort; +package io.github.dunwu.algorithm.sort; /** + * 排序通用泛型接口 + * * @author Zhang Peng */ public interface Sort { + /** * 排序接口 + * * @param list 要排序的数组 */ > void sort(T[] list); + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/SortStrategy.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/SortStrategy.java index 5661368..5e2f7ef 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/SortStrategy.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/SortStrategy.java @@ -1,25 +1,27 @@ -package io.github.dunwu.ds.sort; +package io.github.dunwu.algorithm.sort; -import io.github.dunwu.ds.util.ArrayUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.github.dunwu.algorithm.util.ArrayUtil; +import lombok.extern.slf4j.Slf4j; /** * 使用策略模式,对算法进行包装 + * * @author Zhang Peng */ +@Slf4j public class SortStrategy { - private Sort sort; - private static final Logger logger = LoggerFactory.getLogger(SortStrategy.class); + + private final Sort sort; public SortStrategy(Sort sort) { this.sort = sort; } public void sort(Integer[] list) { - logger.info(this.sort.getClass().getSimpleName() + " 排序开始:"); - logger.info("排序前: {}", ArrayUtil.getArrayString(list, 0, list.length - 1)); + System.out.printf("=================== %s 排序开始 ===================\n", this.sort.getClass().getSimpleName()); + System.out.printf("【排序前】\n%s\n", ArrayUtil.getArrayString(list, 0, list.length - 1)); this.sort.sort(list); - logger.info("排序后: {}", ArrayUtil.getArrayString(list, 0, list.length - 1)); + System.out.printf("【排序后】\n%s\n", ArrayUtil.getArrayString(list, 0, list.length - 1)); } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort.java index e0379d8..3c10699 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort.java @@ -1,13 +1,15 @@ -package io.github.dunwu.ds.sort.strategy; +package io.github.dunwu.algorithm.sort.strategy; -import io.github.dunwu.ds.util.ArrayUtil; -import io.github.dunwu.ds.sort.Sort; +import io.github.dunwu.algorithm.sort.Sort; +import io.github.dunwu.algorithm.util.ArrayUtil; /** * 冒泡排序算法 + * * @author Zhang Peng */ public class BubbleSort implements Sort { + @Override public > void sort(T[] list) { // 要遍历的次数 @@ -21,8 +23,8 @@ public > void sort(T[] list) { list[j] = temp; } } - - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("第 %d 趟:", i + 1)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟", i + 1)); } } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort2.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort2.java index 49b86b3..7bebb12 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort2.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/BubbleSort2.java @@ -1,21 +1,22 @@ -package io.github.dunwu.ds.sort.strategy; +package io.github.dunwu.algorithm.sort.strategy; -import io.github.dunwu.ds.util.ArrayUtil; -import io.github.dunwu.ds.sort.Sort; +import io.github.dunwu.algorithm.sort.Sort; +import io.github.dunwu.algorithm.util.ArrayUtil; /** * 冒泡排序的优化算法 + * * @author Zhang Peng */ public class BubbleSort2 implements Sort { + @Override public > void sort(T[] list) { - // 交换标志 - boolean bChange = false; // 要遍历的次数 for (int i = 0; i < list.length - 1; i++) { - bChange = false; + // 交换标志 + boolean changed = false; // 从后向前依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上 for (int j = list.length - 1; j > i; j--) { // 比较相邻的元素,如果前面的数大于后面的数,则交换 @@ -23,16 +24,17 @@ public > void sort(T[] list) { T temp = list[j - 1]; list[j - 1] = list[j]; list[j] = temp; - bChange = true; + changed = true; } } // 如果标志为false,说明本轮遍历没有交换,已经是有序数列,可以结束排序 - if (false == bChange) { + if (!changed) { break; } - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("第 %d 趟:", i + 1)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟", i + 1)); } } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/HeapSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/HeapSort.java index 476e49e..d403207 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/HeapSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/HeapSort.java @@ -1,13 +1,36 @@ -package io.github.dunwu.ds.sort.strategy; +package io.github.dunwu.algorithm.sort.strategy; -import io.github.dunwu.ds.util.ArrayUtil; -import io.github.dunwu.ds.sort.Sort; +import io.github.dunwu.algorithm.sort.Sort; +import io.github.dunwu.algorithm.util.ArrayUtil; /** * 堆排序算法 + * * @author Zhang Peng */ public class HeapSort implements Sort { + + @Override + public > void sort(T[] list) { + // 循环建立初始堆 + for (int i = list.length / 2; i >= 0; i--) { + adjustHeat(list, i, list.length); + } + + // 进行n-1次循环,完成排序 + for (int i = list.length - 1; i > 0; i--) { + // 最后一个元素和第一元素进行交换 + T temp = list[i]; + list[i] = list[0]; + list[0] = temp; + + // 筛选 R[0] 结点,得到i-1个结点的堆 + adjustHeat(list, 0, i); + + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟", list.length - i)); + } + } + private static > void adjustHeat(T[] array, int parent, int length) { // temp保存当前父节点 T temp = array[parent]; @@ -36,24 +59,4 @@ private static > void adjustHeat(T[] array, int parent, array[parent] = temp; } - @Override - public > void sort(T[] list) { - // 循环建立初始堆 - for (int i = list.length / 2; i >= 0; i--) { - adjustHeat(list, i, list.length); - } - - // 进行n-1次循环,完成排序 - for (int i = list.length - 1; i > 0; i--) { - // 最后一个元素和第一元素进行交换 - T temp = list[i]; - list[i] = list[0]; - list[0] = temp; - - // 筛选 R[0] 结点,得到i-1个结点的堆 - adjustHeat(list, 0, i); - - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("第 %d 趟:", list.length - i)); - } - } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/InsertSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/InsertSort.java index 2e654ce..9d1d49b 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/InsertSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/InsertSort.java @@ -1,18 +1,17 @@ -package io.github.dunwu.ds.sort.strategy; +package io.github.dunwu.algorithm.sort.strategy; -import io.github.dunwu.ds.util.ArrayUtil; -import io.github.dunwu.ds.sort.Sort; +import io.github.dunwu.algorithm.sort.Sort; +import io.github.dunwu.algorithm.util.ArrayUtil; /** * 插入排序算法 + * * @author Zhang Peng */ public class InsertSort implements Sort { + @Override public > void sort(T[] list) { - // 打印第一个元素 - ArrayUtil.debugLogArray(list, 0, 0, String.format("i = %d:\t", 0)); - // 第1个数肯定是有序的,从第2个数开始遍历,依次插入有序序列 for (int i = 1; i < list.length; i++) { int j = 0; @@ -25,7 +24,8 @@ public > void sort(T[] list) { } list[j + 1] = temp; - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("i = %d:\t", i)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟", i)); } } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/MergeSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/MergeSort.java index f20bc2e..56d36ac 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/MergeSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/MergeSort.java @@ -1,9 +1,32 @@ -package io.github.dunwu.ds.sort.strategy; +package io.github.dunwu.algorithm.sort.strategy; -import io.github.dunwu.ds.util.ArrayUtil; -import io.github.dunwu.ds.sort.Sort; +import io.github.dunwu.algorithm.sort.Sort; +import io.github.dunwu.algorithm.util.ArrayUtil; public class MergeSort implements Sort { + + @Override + public > void sort(T[] list) { + for (int gap = 1; gap < list.length; gap = 2 * gap) { + mergeSort(list, gap, list.length); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("gap = %d", gap)); + } + } + + private > void mergeSort(T[] array, int gap, int length) { + int i = 0; + + // 归并gap长度的两个相邻子表 + for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) { + merge(array, i, i + gap - 1, i + 2 * gap - 1); + } + + // 余下两个子表,后者长度小于gap + if (i + gap - 1 < length) { + merge(array, i, i + gap - 1, length - 1); + } + } + private > void merge(T[] array, int low, int mid, int high) { // i是第一段序列的下标 int i = low; @@ -48,26 +71,4 @@ private > void merge(T[] array, int low, int mid, int hi } } - private > void mergeSort(T[] array, int gap, int length) { - int i = 0; - - // 归并gap长度的两个相邻子表 - for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) { - merge(array, i, i + gap - 1, i + 2 * gap - 1); - } - - // 余下两个子表,后者长度小于gap - if (i + gap - 1 < length) { - merge(array, i, i + gap - 1, length - 1); - } - } - - @Override - public > void sort(T[] list) { - for (int gap = 1; gap < list.length; gap = 2 * gap) { - mergeSort(list, gap, list.length); - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("gap = %d", gap)); - } - } - } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/QuickSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/QuickSort.java index 301776f..03ee815 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/QuickSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/QuickSort.java @@ -1,10 +1,11 @@ -package io.github.dunwu.ds.sort.strategy; +package io.github.dunwu.algorithm.sort.strategy; -import io.github.dunwu.ds.util.ArrayUtil; -import io.github.dunwu.ds.sort.Sort; +import io.github.dunwu.algorithm.sort.Sort; +import io.github.dunwu.algorithm.util.ArrayUtil; /** * 快速排序算法 + * * @author Zhang Peng */ public class QuickSort implements Sort { @@ -41,7 +42,7 @@ private > void quickSort(T[] list, int left, int right) // 对数组进行分割,取出下次分割的基准标号 int base = division(list, left, right); - ArrayUtil.debugLogArray(list, left, right, String.format("base = %d: ", list[base])); + ArrayUtil.printArray(list, left, right, String.format("base = %d: ", list[base])); // 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序 quickSort(list, left, base - 1); @@ -55,4 +56,5 @@ private > void quickSort(T[] list, int left, int right) public > void sort(T[] list) { quickSort(list, 0, list.length - 1); } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/SelectionSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/SelectionSort.java index a0ffe52..319cc81 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/SelectionSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/SelectionSort.java @@ -1,13 +1,15 @@ -package io.github.dunwu.ds.sort.strategy; +package io.github.dunwu.algorithm.sort.strategy; -import io.github.dunwu.ds.util.ArrayUtil; -import io.github.dunwu.ds.sort.Sort; +import io.github.dunwu.algorithm.sort.Sort; +import io.github.dunwu.algorithm.util.ArrayUtil; /** * 选择排序算法 + * * @author Zhang Peng */ public class SelectionSort implements Sort { + @Override public > void sort(T[] list) { // 需要遍历获得最小值的次数 @@ -28,7 +30,8 @@ public > void sort(T[] list) { list[index] = list[i]; list[i] = temp; - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("第 %d 趟:", i + 1)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("第 %02d 趟:", i + 1)); } } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/ShellSort.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/ShellSort.java index 866dc66..7b32584 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/ShellSort.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/sort/strategy/ShellSort.java @@ -1,13 +1,15 @@ -package io.github.dunwu.ds.sort.strategy; +package io.github.dunwu.algorithm.sort.strategy; -import io.github.dunwu.ds.util.ArrayUtil; -import io.github.dunwu.ds.sort.Sort; +import io.github.dunwu.algorithm.sort.Sort; +import io.github.dunwu.algorithm.util.ArrayUtil; /** * 希尔排序算法 + * * @author Zhang Peng */ public class ShellSort implements Sort { + @Override public > void sort(T[] list) { int gap = list.length / 2; @@ -25,9 +27,10 @@ public > void sort(T[] list) { list[j + gap] = temp; } - ArrayUtil.debugLogArray(list, 0, list.length - 1, String.format("gap = %d:", gap)); + ArrayUtil.printArray(list, 0, list.length - 1, String.format("gap = %d:", gap)); // 减小增量 gap = gap / 2; } } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/GenericStack.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/GenericStack.java new file mode 100644 index 0000000..ac5bd96 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/GenericStack.java @@ -0,0 +1,75 @@ +package io.github.dunwu.algorithm.stack; + +/** + * 简化版泛型栈 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class GenericStack { + + private int size = 0; + private Node top = null; + + public void push(T value) { + Node node = new Node<>(value, null); + if (top == null) { + top = node; + } else { + node.next = top; + top = node; + } + size++; + } + + public T pop() { + if (top == null) { + return null; + } + T val = top.data; + top = top.next; + size--; + return val; + } + + public T peek() { + if (top == null) { + return null; + } + return top.data; + } + + public int getSize() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + public void printAll() { + Node p = top; + while (p != null) { + System.out.print(p.data + " "); + p = p.next; + } + System.out.println(); + } + + private static class Node { + + private T data; + private Node next; + + public Node(T data, Node next) { + this.data = data; + this.next = next; + } + + public T getData() { + return data; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/package-info.java new file mode 100644 index 0000000..6a2fefd --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/package-info.java @@ -0,0 +1,7 @@ +/** + * 通过「单调栈」解决「下一个更大元素」,「上一个更小元素」等类型问题 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +package io.github.dunwu.algorithm.stack.monotonic; \ No newline at end of file diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240.java" new file mode 100644 index 0000000..d18dd54 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240.java" @@ -0,0 +1,48 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * 496. 下一个更大元素 I + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 下一个更大元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + int[] output1 = s.nextGreaterElement(new int[] { 4, 1, 2 }, new int[] { 1, 3, 4, 2 }); + Assertions.assertArrayEquals(new int[] { -1, 3, -1 }, output1); + int[] output2 = s.nextGreaterElement(new int[] { 2, 4 }, new int[] { 1, 2, 3, 4 }); + Assertions.assertArrayEquals(new int[] { 3, -1 }, output2); + } + + // 采用单调栈解决问题,算法复杂度:O(n) + static class Solution { + + public int[] nextGreaterElement(int[] nums1, int[] nums2) { + Stack stack = new Stack<>(); + Map map = new HashMap<>(); + for (int i = nums2.length - 1; i >= 0; i--) { + while (!stack.isEmpty() && stack.peek() <= nums2[i]) { + stack.pop(); + } + int largerVal = stack.isEmpty() ? -1 : stack.peek(); + map.put(nums2[i], largerVal); + stack.push(nums2[i]); + } + + for (int i = 0; i < nums1.length; i++) { + nums1[i] = map.get(nums1[i]); + } + return nums1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\2402.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\2402.java" new file mode 100644 index 0000000..b2fe027 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\2402.java" @@ -0,0 +1,42 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 503. 下一个更大元素 II + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 下一个更大元素2 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 2, -1, 2 }, s.nextGreaterElements(new int[] { 1, 2, 1 })); + Assertions.assertArrayEquals(new int[] { 2, 3, 4, -1, 4 }, s.nextGreaterElements(new int[] { 1, 2, 3, 4, 3 })); + } + + static class Solution { + + public int[] nextGreaterElements(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = 2 * n - 1; i >= 0; i--) { + int index = i % n; + // 遍历栈,将小于当前元素的值都踢了 + while (!s.isEmpty() && s.peek() <= nums[index]) { + s.pop(); + } + // nums[i] 下一个更大元素在栈顶 + res[index] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[index]); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\345\225\206\345\223\201\346\212\230\346\211\243\345\220\216\347\232\204\346\234\200\347\273\210\344\273\267\346\240\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\345\225\206\345\223\201\346\212\230\346\211\243\345\220\216\347\232\204\346\234\200\347\273\210\344\273\267\346\240\274.java" new file mode 100644 index 0000000..9243ec1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\345\225\206\345\223\201\346\212\230\346\211\243\345\220\216\347\232\204\346\234\200\347\273\210\344\273\267\346\240\274.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 1475. 商品折扣后的最终价格 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 商品折扣后的最终价格 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 4, 2, 4, 2, 3 }, s.finalPrices(new int[] { 8, 4, 6, 2, 3 })); + Assertions.assertArrayEquals(new int[] { 1, 2, 3, 4, 5 }, s.finalPrices(new int[] { 1, 2, 3, 4, 5 })); + Assertions.assertArrayEquals(new int[] { 9, 0, 1, 6 }, s.finalPrices(new int[] { 10, 1, 1, 6 })); + } + + static class Solution { + + public int[] finalPrices(int[] prices) { + int[] res = new int[prices.length]; + Stack stack = new Stack<>(); + for (int i = prices.length - 1; i >= 0; i--) { + while (!stack.isEmpty() && stack.peek() > prices[i]) { + stack.pop(); + } + res[i] = stack.isEmpty() ? prices[i] : prices[i] - stack.peek(); + stack.push(prices[i]); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204.java" new file mode 100644 index 0000000..077f1c8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\234\200\347\237\255\346\227\240\345\272\217\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204.java" @@ -0,0 +1,89 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Stack; + +/** + * 581. 最短无序连续子数组 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 最短无序连续子数组 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.findUnsortedSubarray(new int[] { 2, 6, 4, 8, 10, 9, 15 })); + Assertions.assertEquals(0, s.findUnsortedSubarray(new int[] { 1, 2, 3, 4 })); + Assertions.assertEquals(0, s.findUnsortedSubarray(new int[] { 1 })); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(5, s2.findUnsortedSubarray(new int[] { 2, 6, 4, 8, 10, 9, 15 })); + Assertions.assertEquals(0, s2.findUnsortedSubarray(new int[] { 1, 2, 3, 4 })); + Assertions.assertEquals(0, s2.findUnsortedSubarray(new int[] { 1 })); + } + + // 排序解法 + static class Solution { + + public int findUnsortedSubarray(int[] nums) { + int[] temp = Arrays.copyOf(nums, nums.length); + Arrays.sort(temp); + int left = Integer.MAX_VALUE, right = Integer.MIN_VALUE; + for (int i = 0; i < nums.length; i++) { + if (temp[i] != nums[i]) { + left = i; + break; + } + } + for (int i = nums.length - 1; i >= 0; i--) { + if (temp[i] != nums[i]) { + right = i; + break; + } + } + if (left == Integer.MAX_VALUE && right == Integer.MIN_VALUE) { + // nums 本来就是有序的 + return 0; + } + return right - left + 1; + } + + } + + // 单调栈解法 + static class Solution2 { + + public int findUnsortedSubarray(int[] nums) { + int n = nums.length; + int left = Integer.MAX_VALUE, right = Integer.MIN_VALUE; + // 递增栈,存储元素索引 + Stack incrStk = new Stack<>(); + for (int i = 0; i < n; i++) { + while (!incrStk.isEmpty() && nums[incrStk.peek()] > nums[i]) { + // 弹出的元素都是乱序元素,其中最小的索引就是乱序子数组的左边界 + left = Math.min(left, incrStk.pop()); + } + incrStk.push(i); + } + // 递减栈,存储元素索引 + Stack decrStk = new Stack<>(); + for (int i = n - 1; i >= 0; i--) { + while (!decrStk.isEmpty() && nums[decrStk.peek()] < nums[i]) { + // 弹出的元素都是乱序元素,其中最大的索引就是乱序子数组的右边界 + right = Math.max(right, decrStk.pop()); + } + decrStk.push(i); + } + if (left == Integer.MAX_VALUE && right == Integer.MIN_VALUE) { + // 说明单调栈没有弹出任何元素,即 nums 本来就是有序的 + return 0; + } + return right - left + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\257\217\346\227\245\346\270\251\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\257\217\346\227\245\346\270\251\345\272\246.java" new file mode 100644 index 0000000..774fe37 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\346\257\217\346\227\245\346\270\251\345\272\246.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 739. 每日温度 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 每日温度 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 1, 1, 4, 2, 1, 1, 0, 0 }, + s.dailyTemperatures(new int[] { 73, 74, 75, 71, 69, 72, 76, 73 })); + Assertions.assertArrayEquals(new int[] { 1, 1, 1, 0 }, + s.dailyTemperatures(new int[] { 30, 40, 50, 60 })); + } + + static class Solution { + + public int[] dailyTemperatures(int[] t) { + int[] res = new int[t.length]; + Stack s = new Stack<>(); + for (int i = t.length - 1; i >= 0; i--) { + while (!s.isEmpty() && s.peek()[1] <= t[i]) { + s.pop(); + } + res[i] = s.isEmpty() ? 0 : s.peek()[0] - i; + s.push(new int[] { i, t[i] }); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\347\247\273\346\216\211K\344\275\215\346\225\260\345\255\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\347\247\273\346\216\211K\344\275\215\346\225\260\345\255\227.java" new file mode 100644 index 0000000..aeb64a5 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\347\247\273\346\216\211K\344\275\215\346\225\260\345\255\227.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 402. 移掉 K 位数字 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 移掉K位数字 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("1219", s.removeKdigits("1432219", 3)); + Assertions.assertEquals("200", s.removeKdigits("10200", 1)); + Assertions.assertEquals("0", s.removeKdigits("10", 2)); + } + + static class Solution { + + public String removeKdigits(String num, int k) { + Stack s = new Stack<>(); + for (char c : num.toCharArray()) { + // 单调栈代码模板 + while (!s.isEmpty() && c < s.peek() && k > 0) { + s.pop(); + k--; + } + // 防止 0 作为数字的开头 + if (s.isEmpty() && c == '0') { continue; } + s.push(c); + } + + // 此时栈中元素单调递增,若 k 还没用完的话删掉栈顶元素 + while (!s.isEmpty() && k > 0) { + s.pop(); + k--; + } + // 若最后没剩下数字,就是 0 + if (s.isEmpty()) { return "0"; } + // 将栈中字符转化成字符串 + StringBuilder sb = new StringBuilder(); + while (!s.isEmpty()) { sb.append(s.pop()); } + // 出栈顺序和字符串顺序是反的 + return sb.reverse().toString(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\202\241\347\245\250\344\273\267\346\240\274\350\267\250\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\202\241\347\245\250\344\273\267\346\240\274\350\267\250\345\272\246.java" new file mode 100644 index 0000000..4852eb3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\202\241\347\245\250\344\273\267\346\240\274\350\267\250\345\272\246.java" @@ -0,0 +1,83 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * 901. 股票价格跨度 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 股票价格跨度 { + + public static void main(String[] args) { + StockSpanner stock = new StockSpanner(); + Assertions.assertEquals(1, stock.next(100)); + Assertions.assertEquals(1, stock.next(80)); + Assertions.assertEquals(1, stock.next(60)); + Assertions.assertEquals(2, stock.next(70)); + Assertions.assertEquals(1, stock.next(60)); + Assertions.assertEquals(4, stock.next(75)); + Assertions.assertEquals(6, stock.next(85)); + + StockSpanner2 stock2 = new StockSpanner2(); + Assertions.assertEquals(1, stock2.next(100)); + Assertions.assertEquals(1, stock2.next(80)); + Assertions.assertEquals(1, stock2.next(60)); + Assertions.assertEquals(2, stock2.next(70)); + Assertions.assertEquals(1, stock2.next(60)); + Assertions.assertEquals(4, stock2.next(75)); + Assertions.assertEquals(6, stock2.next(85)); + } + + static class StockSpanner { + + private final List l; + + public StockSpanner() { + l = new ArrayList<>(); + } + + public int next(int price) { + int count = 1; + for (int i = l.size() - 1; i >= 0; i--) { + if (l.get(i) > price) { + break; + } + count++; + } + l.add(price); + return count; + } + + } + + static class StockSpanner2 { + + // int[] 记录 {价格,小于等于该价格的天数} 二元组 + private final Stack s; + + public StockSpanner2() { + s = new Stack<>(); + } + + public int next(int price) { + // 算上当天 + int count = 1; + // 单调栈模板 + while (!s.isEmpty() && s.peek()[0] <= price) { + // 挤掉价格低于 price 的记录 + int[] prev = s.pop(); + count += prev[1]; + } + s.push(new int[] { price, count }); + return count; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\275\246\344\275\215.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\275\246\344\275\215.java" new file mode 100644 index 0000000..8005fcc --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\350\275\246\344\275\215.java" @@ -0,0 +1,70 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Stack; + +/** + * 853. 车队 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 车位 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.carFleet(12, new int[] { 10, 8, 0, 5, 3 }, new int[] { 2, 4, 1, 1, 3 })); + Assertions.assertEquals(1, s.carFleet(10, new int[] { 3 }, new int[] { 3 })); + Assertions.assertEquals(1, s.carFleet(100, new int[] { 0, 2, 4 }, new int[] { 4, 2, 1 })); + } + + static class Solution { + + public int carFleet(int target, int[] position, int[] speed) { + + int n = position.length; + int[][] cars = new int[n][2]; + for (int i = 0; i < n; i++) { + cars[i][0] = position[i]; + cars[i][1] = speed[i]; + } + + // 按照初始位置,从小到大排序 + Arrays.sort(cars, (int[] a, int[] b) -> { + return Integer.compare(a[0], b[0]); + }); + + // 计算每辆车到达终点的时间 + double[] times = new double[n]; + for (int i = 0; i < n; i++) { + int[] car = cars[i]; + times[i] = (double) (target - car[0]) / car[1]; + } + + // 使用单调栈计算车队的数量 + Stack s = new Stack<>(); + for (double t : times) { + while (!s.isEmpty() && t >= s.peek()) { + s.pop(); + } + s.push(t); + } + return s.size(); + + // 避免使用栈模拟,倒序遍历取递增序列就是答案 + // int res = 0; + // double maxTime = 0; + // for (int i = n - 1; i >= 0; i--) { + // if (time[i] > maxTime) { + // maxTime = time[i]; + // res++; + // } + // } + // return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\223\276\350\241\250\344\270\255\347\232\204\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\223\276\350\241\250\344\270\255\347\232\204\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\350\212\202\347\202\271.java" new file mode 100644 index 0000000..cd746cc --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\223\276\350\241\250\344\270\255\347\232\204\344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\350\212\202\347\202\271.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Stack; + +/** + * 1019. 链表中的下一个更大节点 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 链表中的下一个更大节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 5, 5, 0 }, + s.nextLargerNodes(ListNode.buildList(2, 1, 5))); + Assertions.assertArrayEquals(new int[] { 7, 0, 5, 5, 0 }, + s.nextLargerNodes(ListNode.buildList(2, 7, 4, 3, 5))); + } + + static class Solution { + + public int[] nextLargerNodes(ListNode head) { + // 把单链表转化成数组,方便通过索引访问 + ArrayList nums = new ArrayList<>(); + ListNode p = head; + while (p != null) { + nums.add(p.val); + p = p.next; + } + + // 存放答案的数组 + int[] res = new int[nums.size()]; + Stack stack = new Stack<>(); + // 单调栈模板,求下一个更大元素,从后往前遍历 + for (int i = nums.size() - 1; i >= 0; i--) { + while (!stack.isEmpty() && stack.peek() <= nums.get(i)) { + stack.pop(); + } + // 本题要求没有下一个更大元素时返回 0 + res[i] = stack.isEmpty() ? 0 : stack.peek(); + stack.push(nums.get(i)); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\230\237\345\210\227\344\270\255\345\217\257\344\273\245\347\234\213\345\210\260\347\232\204\344\272\272\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\230\237\345\210\227\344\270\255\345\217\257\344\273\245\347\234\213\345\210\260\347\232\204\344\272\272\346\225\260.java" new file mode 100644 index 0000000..8257b94 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/monotonic/\351\230\237\345\210\227\344\270\255\345\217\257\344\273\245\347\234\213\345\210\260\347\232\204\344\272\272\346\225\260.java" @@ -0,0 +1,45 @@ +package io.github.dunwu.algorithm.stack.monotonic; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 1944. 队列中可以看到的人数 + * + * @author Zhang Peng + * @date 2025-12-19 + */ +public class 队列中可以看到的人数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(new int[] { 3, 1, 2, 1, 1, 0 }, s.canSeePersonsCount(new int[] { 10, 6, 8, 5, 11, 9 })); + Assertions.assertEquals(new int[] { 4, 1, 1, 1, 0 }, s.canSeePersonsCount(new int[] { 5, 1, 2, 3, 10 })); + } + + static class Solution { + + public int[] canSeePersonsCount(int[] heights) { + int n = heights.length; + int[] res = new int[n]; + // int[] 记录 {身高,小于等于该身高的人数} 二元组 + Stack stk = new Stack<>(); + for (int i = n - 1; i >= 0; i--) { + // 记录右侧比自己矮的人 + int count = 0; + // 单调栈模板,计算下一个更大或相等元素(身高) + while (!stk.isEmpty() && heights[i] > stk.peek()) { + stk.pop(); + count++; + } + // 不仅可以看到比自己矮的人,如果后面存在更高的的人,也可以看到这个高人 + res[i] = stk.isEmpty() ? count : count + 1; + stk.push(heights[i]); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/template/\345\215\225\350\260\203\346\240\210\347\256\227\346\263\225\346\250\241\346\235\277.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/template/\345\215\225\350\260\203\346\240\210\347\256\227\346\263\225\346\250\241\346\235\277.java" new file mode 100644 index 0000000..99192ab --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/template/\345\215\225\350\260\203\346\240\210\347\256\227\346\263\225\346\250\241\346\235\277.java" @@ -0,0 +1,172 @@ +package io.github.dunwu.algorithm.stack.template; + +import java.util.Stack; + +/** + * 单调栈算法模板 + * + * @author Zhang Peng + * @date 2025-12-19 + */ +public class 单调栈算法模板 { + + /** + * 下一个更大的元素:计算 nums 中每个元素的下一个更大元素 + */ + int[] nextGreaterElement(int[] nums) { + int n = nums.length; + // 存放答案的数组 + int[] res = new int[n]; + Stack s = new Stack<>(); + // 因为是求 nums[i] 后面的元素,所以倒着往栈里放 + for (int i = n - 1; i >= 0; i--) { + // 删掉 nums[i] 后面较小的元素 + while (!s.isEmpty() && s.peek() <= nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 身后的更大元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 下一个更大或相等的元素:计算 nums 中每个元素的下一个更大或相等的元素 + */ + int[] nextGreaterOrEqualElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = n - 1; i >= 0; i--) { + // 把这里改成 < 号 + while (!s.isEmpty() && s.peek() < nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 身后的大于等于 nums[i] 的元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 下一个更小的元素:计算 nums 中每个元素的下一个更小的元素 + */ + int[] nextLessElement(int[] nums) { + int n = nums.length; + // 存放答案的数组 + int[] res = new int[n]; + Stack s = new Stack<>(); + // 倒着往栈里放 + for (int i = n - 1; i >= 0; i--) { + // 删掉 nums[i] 后面较大的元素 + while (!s.isEmpty() && s.peek() >= nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 身后的更小元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 下一个更小或相等的元素:计算 nums 中每个元素的下一个更小或相等的元素 + */ + int[] nextLessOrEqualElement(int[] nums) { + int n = nums.length; + // 存放答案的数组 + int[] res = new int[n]; + Stack s = new Stack<>(); + // 倒着往栈里放 + for (int i = n - 1; i >= 0; i--) { + // 删掉 nums[i] 后面较大的元素 + while (!s.isEmpty() && s.peek() > nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 身后的更小或相等元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 上一个更大元素:计算 nums 中每个元素的上一个更大元素 + */ + int[] prevGreaterElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + // 因为是求 nums[i] 前面的元素,所以正着往栈里放 + for (int i = 0; i < n; i++) { + // 删掉 nums[i] 前面较小的元素 + while (!s.isEmpty() && s.peek() <= nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 前面的更大元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 上一个更大或相等的元素:计算 nums 中每个元素的上一个更大或相等元素 + */ + int[] prevGreaterOrEqualElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = 0; i < n; i++) { + // 注意不等号 + while (!s.isEmpty() && s.peek() < nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 前面的更大或相等元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 上一个更小的元素:计算 nums 中每个元素的上一个更小的元素 + */ + int[] prevLessElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = 0; i < n; i++) { + // 把 nums[i] 之前的较大元素删除 + while (!s.isEmpty() && s.peek() >= nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 前面的更小元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + + /** + * 上一个更小或相等的元素:计算 nums 中每个元素的上一个更小或相等元素 + */ + int[] prevLessOrEqualElement(int[] nums) { + int n = nums.length; + int[] res = new int[n]; + Stack s = new Stack<>(); + for (int i = 0; i < n; i++) { + // 注意不等号 + while (!s.isEmpty() && s.peek() > nums[i]) { + s.pop(); + } + // 现在栈顶就是 nums[i] 前面的更小或相等元素 + res[i] = s.isEmpty() ? -1 : s.peek(); + s.push(nums[i]); + } + return res; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\226\207\344\273\266\347\232\204\346\234\200\351\225\277\347\273\235\345\257\271\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\226\207\344\273\266\347\232\204\346\234\200\351\225\277\347\273\235\345\257\271\350\267\257\345\276\204.java" new file mode 100644 index 0000000..e0bde55 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\226\207\344\273\266\347\232\204\346\234\200\351\225\277\347\273\235\345\257\271\350\267\257\345\276\204.java" @@ -0,0 +1,51 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Deque; +import java.util.LinkedList; + +/** + * 150. 逆波兰表达式求值 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 文件的最长绝对路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(20, s.lengthLongestPath("dir\\n\\tsubdir1\\n\\tsubdir2\\n\\t\\tfile.ext")); + Assertions.assertEquals(32, s.lengthLongestPath( + "dir\\n\\tsubdir1\\n\\t\\tfile1.ext\\n\\t\\tsubsubdir1\\n\\tsubdir2\\n\\t\\tsubsubdir2\\n\\t\\t\\tfile2.ext")); + Assertions.assertEquals(0, s.lengthLongestPath("a")); + Assertions.assertEquals(12, s.lengthLongestPath("file1.txt\\nfile2.txt\\nlongfile.txt")); + } + + static class Solution { + + public int lengthLongestPath(String input) { + // 这个栈存储之前的父路径。实际上这里只用存父路径的长度就够了,这个优化留给你吧 + Deque stack = new LinkedList<>(); + int maxLen = 0; + for (String part : input.split("\n")) { + int level = part.lastIndexOf("\t") + 1; + // 让栈中只保留当前目录的父路径 + while (level < stack.size()) { + stack.removeLast(); + } + stack.addLast(part.substring(level)); + // 如果是文件,就计算路径长度 + if (part.contains(".")) { + int sum = stack.stream().mapToInt(String::length).sum(); + // 加上父路径的分隔符 + sum += stack.size() - 1; + maxLen = Math.max(maxLen, sum); + } + } + return maxLen; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\244\247\351\242\221\347\216\207\346\240\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\244\247\351\242\221\347\216\207\346\240\210.java" new file mode 100644 index 0000000..9f6ec25 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\244\247\351\242\221\347\216\207\346\240\210.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Stack; +import java.util.TreeMap; + +/** + * 895. 最大频率栈 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 最大频率栈 { + + public static void main(String[] args) { + FreqStack s1 = new FreqStack(); + s1.push(5);//堆栈为 [5] + s1.push(7);//堆栈是 [5,7] + s1.push(5);//堆栈是 [5,7,5] + s1.push(7);//堆栈是 [5,7,5,7] + s1.push(4);//堆栈是 [5,7,5,7,4] + s1.push(5);//堆栈是 [5,7,5,7,4,5] + Assertions.assertEquals(5, s1.pop()); //返回 5 ,因为 5 出现频率最高。堆栈变成 [5,7,5,7,4] + Assertions.assertEquals(7, s1.pop()); //返回 7 ,因为 5 和 7 出现频率最高,但7最接近顶部。堆栈变成 [5,7,5,4]。 + Assertions.assertEquals(5, s1.pop()); //返回 5 ,因为 5 出现频率最高。堆栈变成 [5,7,4]。 + Assertions.assertEquals(4, s1.pop()); //返回 4 ,因为 4, 5 和 7 出现频率最高,但 4 是最接近顶部的。堆栈变成 [5,7]。 + } + + static class FreqStack { + + private HashMap valToFreq; + private TreeMap> freqToValStack; + + public FreqStack() { + valToFreq = new HashMap<>(); + freqToValStack = new TreeMap<>(); + } + + public void push(int val) { + valToFreq.put(val, valToFreq.getOrDefault(val, 0) + 1); + Integer freq = this.valToFreq.get(val); + freqToValStack.putIfAbsent(freq, new Stack<>()); + freqToValStack.get(freq).push(val); + } + + public int pop() { + Integer maxFreq = freqToValStack.lastKey(); + Stack stack = freqToValStack.get(maxFreq); + Integer val = stack.pop(); + if (stack.empty()) { freqToValStack.remove(maxFreq); } + valToFreq.put(val, valToFreq.getOrDefault(val, 0) - 1); + return val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\210.java" new file mode 100644 index 0000000..6775a69 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\200\345\260\217\346\240\210.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 面试题 03.02. 栈的最小值 + * + * @author Zhang Peng + * @date 2025-10-20 + */ +public class 最小栈 { + + public static void main(String[] args) { + MinStack minStack = new MinStack(); + minStack.push(-2); + minStack.push(0); + minStack.push(-3); + Assertions.assertEquals(-3, minStack.getMin()); + minStack.pop(); + Assertions.assertEquals(0, minStack.top()); + Assertions.assertEquals(-2, minStack.getMin()); + } + + static class MinStack { + + Stack stack; + Stack minStack; + + public MinStack() { + stack = new Stack<>(); + minStack = new Stack<>(); + } + + public void push(int val) { + stack.push(val); + if (minStack.isEmpty() || val < minStack.peek()) { + minStack.push(val); + } else { + minStack.push(minStack.peek());; + } + } + + public void pop() { + stack.pop(); + minStack.pop(); + } + + public int top() { + return stack.peek(); + } + + public int getMin() { + return minStack.peek(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.java" new file mode 100644 index 0000000..1601e5f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\234\211\346\225\210\347\232\204\346\213\254\345\217\267.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 20. 有效的括号 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 有效的括号 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isValid("()")); + Assertions.assertTrue(s.isValid("{[]}")); + Assertions.assertFalse(s.isValid("([)]")); + Assertions.assertFalse(s.isValid("([)")); + Assertions.assertFalse(s.isValid("((")); + Assertions.assertTrue(s.isValid("(())")); + } + + static class Solution { + + public boolean isValid(String s) { + Stack stack = new Stack<>(); + for (char c : s.toCharArray()) { + switch (c) { + case ')': + if (stack.isEmpty()) { return false; } + if (stack.pop() != '(') { return false; } + break; + case ']': + if (stack.isEmpty()) { return false; } + if (stack.pop() != '[') { return false; } + break; + case '}': + if (stack.isEmpty()) { return false; } + if (stack.pop() != '{') { return false; } + break; + default: + stack.push(c); + break; + } + } + return stack.isEmpty(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\240\210\346\216\222\345\272\217.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\240\210\346\216\222\345\272\217.java" new file mode 100644 index 0000000..40e5a99 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\240\210\346\216\222\345\272\217.java" @@ -0,0 +1,71 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 面试题 03.05. 栈排序 + * + * @author Zhang Peng + * @date 2025-11-26 + */ +public class 栈排序 { + + public static void main(String[] args) { + SortedStack s = new SortedStack(); + s.push(1); + s.push(2); + Assertions.assertEquals(1, s.peek()); + s.pop(); + Assertions.assertEquals(2, s.peek()); + + SortedStack s2 = new SortedStack(); + s2.pop(); + s2.pop(); + s2.push(1); + s2.pop(); + Assertions.assertTrue(s2.isEmpty()); + } + + static class SortedStack { + + private Stack s; + private Stack t; + + public SortedStack() { + s = new Stack<>(); + t = new Stack<>(); + } + + public void push(int val) { + if (s.isEmpty()) { + s.push(val); + return; + } + while (!s.isEmpty() && s.peek() < val) { + t.push(s.pop()); + } + s.push(val); + while (!t.isEmpty()) { + s.push(t.pop()); + } + } + + public void pop() { + if (!s.isEmpty()) { + s.pop(); + } + } + + public int peek() { + return s.isEmpty() ? -1 : s.peek(); + } + + public boolean isEmpty() { + return s.isEmpty(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\243\222\347\220\203\346\257\224\350\265\233.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\243\222\347\220\203\346\257\224\350\265\233.java" new file mode 100644 index 0000000..3ae17cf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\243\222\347\220\203\346\257\224\350\265\233.java" @@ -0,0 +1,56 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 682. 棒球比赛 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 棒球比赛 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(30, s.calPoints(new String[] { "5", "2", "C", "D", "+" })); + Assertions.assertEquals(27, s.calPoints(new String[] { "5", "-2", "4", "C", "D", "9", "+", "+" })); + } + + static class Solution { + + public int calPoints(String[] operations) { + Stack stack = new Stack<>(); + for (String op : operations) { + switch (op) { + case "C": + stack.pop(); + break; + case "D": + stack.push(stack.peek() * 2); + break; + case "+": + int cur = stack.pop(); + int prev = stack.pop(); + int next = prev + cur; + stack.push(prev); + stack.push(cur); + stack.push(next); + break; + default: + stack.push(Integer.valueOf(op)); + break; + } + } + + int res = 0; + while (!stack.isEmpty()) { + res += stack.pop(); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.java" new file mode 100644 index 0000000..5fee00b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\346\257\224\350\276\203\345\220\253\351\200\200\346\240\274\347\232\204\345\255\227\347\254\246\344\270\262.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 844. 比较含退格的字符串 + * + * @author Zhang Peng + * @since 2020-06-09 + */ +public class 比较含退格的字符串 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.backspaceCompare("ab#c", "ad#c")); + Assertions.assertTrue(s.backspaceCompare("ab##", "c#d#")); + Assertions.assertTrue(s.backspaceCompare("a##c", "#a#c")); + Assertions.assertFalse(s.backspaceCompare("a#c", "b")); + } + + static class Solution { + + public boolean backspaceCompare(String s, String t) { + Stack a = new Stack<>(); + Stack b = new Stack<>(); + for (char c : s.toCharArray()) { + if (c == '#') { + if (!a.isEmpty()) { a.pop(); } + } else { + a.push(c); + } + } + for (char c : t.toCharArray()) { + if (c == '#') { + if (!b.isEmpty()) { b.pop(); } + } else { + b.push(c); + } + } + + if (a.size() != b.size()) { return false; } + while (!a.isEmpty()) { + if (a.pop() != b.pop()) { return false; } + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\347\232\204\345\211\215\350\277\233\345\220\216\351\200\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\347\232\204\345\211\215\350\277\233\345\220\216\351\200\200.java" new file mode 100644 index 0000000..c12f789 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\347\232\204\345\211\215\350\277\233\345\220\216\351\200\200.java" @@ -0,0 +1,192 @@ +package io.github.dunwu.algorithm.stack; + +/** + * 使用前后栈实现浏览器的前进后退。 + * + * @author chinalwb + */ +public class 用栈实现浏览器的前进后退 { + + public static void main(String[] args) { + 用栈实现浏览器的前进后退 browser = new 用栈实现浏览器的前进后退(); + browser.open("http://www.baidu.com"); + browser.open("http://news.baidu.com/"); + browser.open("http://news.baidu.com/ent"); + browser.goBack(); + browser.goBack(); + browser.goForward(); + browser.open("http://www.qq.com"); + browser.goForward(); + browser.goBack(); + browser.goForward(); + browser.goBack(); + browser.goBack(); + browser.goBack(); + browser.goBack(); + browser.checkCurrentPage(); + } + + private String currentPage; + private LinkedListBasedStack backStack; + private LinkedListBasedStack forwardStack; + + public 用栈实现浏览器的前进后退() { + this.backStack = new LinkedListBasedStack(); + this.forwardStack = new LinkedListBasedStack(); + } + + public void open(String url) { + if (this.currentPage != null) { + this.backStack.push(this.currentPage); + this.forwardStack.clear(); + } + showUrl(url, "Open"); + } + + public boolean canGoBack() { + return this.backStack.size() > 0; + } + + public boolean canGoForward() { + return this.forwardStack.size() > 0; + } + + public String goBack() { + if (this.canGoBack()) { + this.forwardStack.push(this.currentPage); + String backUrl = this.backStack.pop(); + showUrl(backUrl, "Back"); + return backUrl; + } + + System.out.println("* Cannot go back, no pages behind."); + return null; + } + + public String goForward() { + if (this.canGoForward()) { + this.backStack.push(this.currentPage); + String forwardUrl = this.forwardStack.pop(); + showUrl(forwardUrl, "Foward"); + return forwardUrl; + } + + System.out.println("** Cannot go forward, no pages ahead."); + return null; + } + + public void showUrl(String url, String prefix) { + this.currentPage = url; + System.out.println(prefix + " page == " + url); + } + + public void checkCurrentPage() { + System.out.println("Current page is: " + this.currentPage); + } + + /** + * A LinkedList based Stack implementation. + */ + public static class LinkedListBasedStack { + + // public static void main(String[] args) { + // LinkedListBasedStack stack = new LinkedListBasedStack(); + // stack.push("A"); + // stack.push("B"); + // stack.push("C"); + // stack.pop(); + // stack.push("D"); + // stack.push("E"); + // stack.pop(); + // stack.push("F"); + // stack.print(); + // + //// String data = stack.getTopData(); + //// System.out.println("Top data == " + data); + // } + + private int size; + private Node top; + + static Node createNode(String data, Node next) { + return new Node(data, next); + } + + public void clear() { + this.top = null; + this.size = 0; + } + + public void push(String data) { + Node node = createNode(data, null); + if (top == null) top = node; + node.next = top; + top = node; + size++; + } + + public String pop() { + if (top == null) { + return null; + } + + String val = top.data; + top = top.next; + return val; + } + + public String getTopData() { + if (top == null) return null; + return top.data; + } + + public int size() { + return this.size; + } + + public void print() { + System.out.println("Print stack:"); + Node currentNode = this.top; + while (currentNode != null) { + String data = currentNode.getData(); + System.out.print(data + "\t"); + currentNode = currentNode.next; + } + System.out.println(); + } + + public static class Node { + + private String data; + private Node next; + + public Node(String data) { + this(data, null); + } + + public Node(String data, Node next) { + this.data = data; + this.next = next; + } + + public void setData(String data) { + this.data = data; + } + + public String getData() { + return this.data; + } + + public void setNext(Node next) { + this.next = next; + } + + public Node getNext() { + return this.next; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.java" new file mode 100644 index 0000000..3051ecf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\224\250\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.java" @@ -0,0 +1,81 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 232. 用栈实现队列 + * + * @author Zhang Peng + * @since 2020-01-18 + */ +public class 用栈实现队列 { + + public static void main(String[] args) { + + MyQueue q1 = new MyQueue(); + q1.push(1); // queue is: [1] + q1.push(2); // queue is: [1, 2] (leftmost is front of the queue) + Assertions.assertEquals(1, q1.peek()); + Assertions.assertEquals(1, q1.pop()); + Assertions.assertFalse(q1.empty()); + Assertions.assertEquals(2, q1.pop()); + Assertions.assertTrue(q1.empty()); + + MyQueue q2 = new MyQueue(); + q2.push(1); + q2.push(2); + Assertions.assertEquals(1, q2.pop()); + q2.push(3); + q2.push(4); + Assertions.assertEquals(2, q2.pop()); + Assertions.assertEquals(3, q2.peek()); + + MyQueue q3 = new MyQueue(); + int max = 10; + for (int i = 1; i <= max; i++) { + q3.push(i); + } + for (int i = 1; i <= max; i++) { + Assertions.assertEquals(i, q3.peek()); + Assertions.assertEquals(i, q3.pop()); + } + } + + static class MyQueue { + + private Stack s1; + private Stack s2; + + public MyQueue() { + s1 = new Stack<>(); + s2 = new Stack<>(); + } + + public void push(int x) { + s1.push(x); + } + + public int pop() { + peek(); + Integer top = s2.pop(); + return top == null ? 0 : top; + } + + public int peek() { + if (s2.isEmpty()) { + while (!s1.isEmpty()) { + s2.push(s1.pop()); + } + } + return s2.isEmpty() ? 0 : s2.peek(); + } + + public boolean empty() { + return s1.isEmpty() && s2.isEmpty(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\256\200\345\214\226\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\256\200\345\214\226\350\267\257\345\276\204.java" new file mode 100644 index 0000000..aa7357b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\347\256\200\345\214\226\350\267\257\345\276\204.java" @@ -0,0 +1,50 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 71. 简化路径 + * + * @author Zhang Peng + * @since 2025-08-08 + */ +public class 简化路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals("/home", s.simplifyPath("/home/")); + Assertions.assertEquals("/home/foo", s.simplifyPath("/home//foo/")); + Assertions.assertEquals("/home/user/Pictures", s.simplifyPath("/home/user/Documents/../Pictures")); + Assertions.assertEquals("/", s.simplifyPath("/../")); + Assertions.assertEquals("/.../b/d", s.simplifyPath("/.../a/../b/c/../d/./")); + } + + static class Solution { + + public String simplifyPath(String path) { + String[] parts = path.split("/"); + Stack stk = new Stack<>(); + // 借助栈计算最终的文件夹路径 + for (String part : parts) { + if (part.isEmpty() || part.equals(".")) { + continue; + } + if (part.equals("..")) { + if (!stk.isEmpty()) stk.pop(); + continue; + } + stk.push(part); + } + // 栈中存储的文件夹组成路径 + String res = ""; + while (!stk.isEmpty()) { + res = "/" + stk.pop() + res; + } + return res.isEmpty() ? "/" : res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.java" new file mode 100644 index 0000000..e83ae8f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.stack; + +import org.junit.jupiter.api.Assertions; + +import java.util.Stack; + +/** + * 150. 逆波兰表达式求值 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 逆波兰表达式求值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(9, s.evalRPN(new String[] { "2", "1", "+", "3", "*" })); + Assertions.assertEquals(6, s.evalRPN(new String[] { "4", "13", "5", "/", "+" })); + Assertions.assertEquals(22, + s.evalRPN(new String[] { "10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+" })); + } + + static class Solution { + + public int evalRPN(String[] tokens) { + Stack stack = new Stack<>(); + for (String token : tokens) { + if ("+".equals(token)) { + Integer numB = stack.pop(); + Integer numA = stack.pop(); + stack.push(numA + numB); + } else if ("-".equals(token)) { + Integer numB = stack.pop(); + Integer numA = stack.pop(); + stack.push(numA - numB); + } else if ("*".equals(token)) { + Integer numB = stack.pop(); + Integer numA = stack.pop(); + stack.push(numA * numB); + } else if ("/".equals(token)) { + Integer numB = stack.pop(); + Integer numA = stack.pop(); + stack.push(numA / numB); + } else { + stack.push(Integer.parseInt(token)); + } + } + return stack.pop(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\207\215\346\216\222\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\207\215\346\216\222\351\223\276\350\241\250.java" new file mode 100644 index 0000000..48cdd0e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/\351\207\215\346\216\222\351\223\276\350\241\250.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.stack; + +import io.github.dunwu.algorithm.linkedlist.ListNode; +import org.junit.jupiter.api.Assertions; + +import java.util.List; +import java.util.Stack; + +/** + * 143. 重排链表 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 重排链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + ListNode input = ListNode.buildList(1, 2, 3, 4); + s.reorderList(input); + List list = ListNode.toList(input); + Assertions.assertArrayEquals(new Integer[] { 1, 4, 2, 3 }, list.toArray()); + + ListNode input2 = ListNode.buildList(1, 2, 3, 4, 5); + s.reorderList(input2); + List list2 = ListNode.toList(input2); + Assertions.assertArrayEquals(new Integer[] { 1, 5, 2, 4, 3 }, list2.toArray()); + } + + static class Solution { + + public void reorderList(ListNode head) { + Stack stack = new Stack<>(); + // 先把所有节点装进栈里,得到倒序结果 + ListNode p = head; + while (p != null) { + stack.push(p); + p = p.next; + } + + p = head; + while (p != null) { + // 链表尾部的节点 + ListNode last = stack.pop(); + ListNode next = p.next; + if (last == next || last.next == next) { + // 结束条件,链表节点数为奇数或偶数时均适用 + last.next = null; + break; + } + p.next = last; + last.next = next; + p = next; + } + } + + } + +} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/AddBinary.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/AddBinary.java similarity index 94% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/str/AddBinary.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/AddBinary.java index d329be7..2691220 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/AddBinary.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/AddBinary.java @@ -1,6 +1,7 @@ -package io.github.dunwu.ds.str; +package io.github.dunwu.algorithm.str; // 【二进制求和】 + // // 给定两个二进制字符串,返回他们的和(用二进制表示)。 // @@ -15,12 +16,12 @@ // 输入: a = "1010", b = "1011" // 输出: "10101" - /** * @author Zhang Peng - * @date 2018-11-04 + * @since 2018-11-04 */ public class AddBinary { + public static String addBinary(String a, String b) { StringBuilder sb = new StringBuilder(); int i = a.length() - 1, j = b.length() - 1, carry = 0; @@ -40,4 +41,5 @@ public static String addBinary(String a, String b) { } return sb.reverse().toString(); } + } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ImplementStrstr.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ImplementStrstr.java similarity index 96% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/str/ImplementStrstr.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ImplementStrstr.java index ba68079..7b18d18 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ImplementStrstr.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ImplementStrstr.java @@ -1,5 +1,4 @@ -package io.github.dunwu.ds.str; - +package io.github.dunwu.algorithm.str; // 【实现 strStr() 函数】 // @@ -19,12 +18,12 @@ // // 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。 - /** * @author Zhang Peng - * @date 2018-11-05 + * @since 2018-11-05 */ public class ImplementStrstr { + public static int strStr(String haystack, String needle) { if (haystack.equals(needle)) { return 0; @@ -63,4 +62,5 @@ public static int strStr(String haystack, String needle) { } return -1; } + } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/LongestCommonPrefix.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/LongestCommonPrefix.java similarity index 95% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/str/LongestCommonPrefix.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/LongestCommonPrefix.java index d3b6025..4e76d0d 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/LongestCommonPrefix.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/LongestCommonPrefix.java @@ -1,6 +1,7 @@ -package io.github.dunwu.ds.str; +package io.github.dunwu.algorithm.str; // 【最长公共前缀】 + // // 编写一个函数来查找字符串数组中的最长公共前缀。 // @@ -19,12 +20,12 @@ // // 所有输入只包含小写字母 a-z 。 - /** * @author Zhang Peng - * @date 2018-11-05 + * @since 2018-11-05 */ public class LongestCommonPrefix { + public static String longestCommonPrefix(String[] strs) { if (strs == null || strs.length == 0) { return ""; @@ -56,4 +57,5 @@ public static String longestCommonPrefix(String[] strs) { } return sb.toString(); } + } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseString.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ReverseString.java similarity index 90% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseString.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ReverseString.java index ad6809e..88400f0 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseString.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ReverseString.java @@ -1,6 +1,7 @@ -package io.github.dunwu.ds.str; +package io.github.dunwu.algorithm.str; // 【反转字符串】 + // // 编写一个函数,其作用是将输入的字符串反转过来。 // @@ -13,12 +14,12 @@ // 输入: "A man, a plan, a canal: Panama" // 输出: "amanaP :lanac a ,nalp a ,nam A" - /** * @author Zhang Peng - * @date 2018-11-05 + * @since 2018-11-05 */ public class ReverseString { + public static String reverseString(String s) { StringBuilder sb = new StringBuilder(); @@ -28,4 +29,5 @@ public static String reverseString(String s) { return sb.toString(); } + } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ReverseWordsInAString.java similarity index 90% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ReverseWordsInAString.java index 008bb05..ebceee6 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ReverseWordsInAString.java @@ -1,6 +1,7 @@ -package io.github.dunwu.ds.str; +package io.github.dunwu.algorithm.str; // 【反转字符串中的单词】 + // // 给定一个字符串,逐个翻转字符串中的每个单词。 // @@ -15,12 +16,12 @@ // 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 // 进阶: 请选用C语言的用户尝试使用 O(1) 空间复杂度的原地解法。 - /** * @author Zhang Peng - * @date 2018-11-05 + * @since 2018-11-05 */ public class ReverseWordsInAString { + public static String reverseWords(String s) { if (s == null) { return null; @@ -37,6 +38,15 @@ public static String reverseWords(String s) { return cleanSpaces(a, n); } + // reverse a[] from a[i] to a[j] + private static void reverse(char[] a, int i, int j) { + while (i < j) { + char t = a[i]; + a[i++] = a[j]; + a[j--] = t; + } + } + private static void reverseWords(char[] a, int n) { int i = 0, j = 0; @@ -58,28 +68,20 @@ private static String cleanSpaces(char[] a, int n) { while (j < n) { while (j < n && a[j] == ' ') { - j++; // skip spaces + j++; // skip spaces } while (j < n && a[j] != ' ') { a[i++] = a[j++]; // keep non spaces } while (j < n && a[j] == ' ') { - j++; // skip spaces + j++; // skip spaces } if (j < n) { - a[i++] = ' '; // keep only one space + a[i++] = ' '; // keep only one space } } return new String(a).substring(0, i); } - // reverse a[] from a[i] to a[j] - private static void reverse(char[] a, int i, int j) { - while (i < j) { - char t = a[i]; - a[i++] = a[j]; - a[j--] = t; - } - } } diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString3.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ReverseWordsInAString3.java similarity index 94% rename from codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString3.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ReverseWordsInAString3.java index 8e06b5c..2e2ab6b 100644 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString3.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ReverseWordsInAString3.java @@ -1,6 +1,7 @@ -package io.github.dunwu.ds.str; +package io.github.dunwu.algorithm.str; // 【反转字符串中的单词 III】 + // // 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 // @@ -10,12 +11,12 @@ // 输出: "s'teL ekat edoCteeL tsetnoc" // 注意:在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。 - /** * @author Zhang Peng - * @date 2018-11-05 + * @since 2018-11-05 */ public class ReverseWordsInAString3 { + public static String reverseWords(String s) { StringBuilder sb = new StringBuilder(); String[] strs = s.split(" "); @@ -38,4 +39,5 @@ public static String reverseWords(String s) { return sb.toString(); } + } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/StringAlgorithm.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/StringAlgorithm.java new file mode 100644 index 0000000..b0a3d12 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/StringAlgorithm.java @@ -0,0 +1,293 @@ +package io.github.dunwu.algorithm.str; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Zhang Peng + * @since 2020-01-18 + */ +public class StringAlgorithm { + + /** + * 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 + *

+ * 示例 1: + *

+ * 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 示例 2: + *

+ * 输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 示例 3: + *

+ * 输入: "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 + *

+ * 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 + * + * @see 无重复字符的最长子串 + */ + public static int lengthOfLongestSubstring(String s) { + if (null == s || s.length() == 0) { + return 0; + } + + int max = 0; + int left = 0; + Map map = new HashMap<>(); + for (int i = 0; i < s.length(); i++) { + if (map.containsKey(s.charAt(i))) { + left = Math.max(left, map.get(s.charAt(i)) + 1); + } + map.put(s.charAt(i), i); + max = Math.max(max, i - left + 1); + } + return max; + } + + /** + * 编写一个函数来查找字符串数组中的最长公共前缀。 + *

+ * 如果不存在公共前缀,返回空字符串 ""。 + *

+ * 示例 1: + *

+ * 输入: ["flower","flow","flight"] 输出: "fl" 示例 2: + *

+ * 输入: ["dog","racecar","car"] 输出: "" 解释: 输入不存在公共前缀。 说明: + *

+ * 所有输入只包含小写字母 a-z 。 + * + * @see 最长公共前缀 + */ + public static String longestCommonPrefix(String[] array) { + if (array == null || array.length == 0) { + return ""; + } else if (array.length == 1) { + return array[0]; + } else { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < array[0].length(); i++) { + char c = array[0].charAt(i); + boolean end = false; + for (int index = 1; index < array.length; index++) { + if (array[index].length() - 1 < i) { + end = true; + break; + } + + if (array[index].charAt(i) != c) { + end = true; + break; + } + } + if (end) { + break; + } else { + sb.append(c); + } + } + return sb.toString(); + } + } + + /** + * 给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。 + *

+ * 换句话说,第一个字符串的排列之一是第二个字符串的子串。 + *

+ * 示例1: 输入: s1 = "ab" s2 = "eidbaooo" 输出: True 解释: s2 包含 s1 的排列之一 ("ba"). + *

+ * 示例2: 输入: s1= "ab" s2 = "eidboaoo" 输出: False + *

+ * 注意:输入的字符串只包含小写字母,两个字符串的长度都在 [1, 10,000] 之间 + * + * @see 字符串的排列 + */ + public static boolean checkInclusion(String s1, String s2) { + if (s1 == null || s1.length() == 0 || s2 == null || s2.length() == 0) { + return false; + } + + int len1 = s1.length(); + int len2 = s2.length(); + + // 字母命中数统计 + int[] count1 = new int[26]; + int[] count2 = new int[26]; + + for (char c : s1.toCharArray()) { + count1[c - 'a']++; + } + + for (int i = 0; i < len2; i++) { + if (i >= len1) { + count2[s2.charAt(i - len1) - 'a']--; + } + + count2[s2.charAt(i) - 'a']++; + if (Arrays.equals(count1, count2)) { + return true; + } + } + return false; + } + + /** + * 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 + *

+ * 示例 1: + *

+ * 输入: num1 = "2", num2 = "3" 输出: "6" 示例 2: + *

+ * 输入: num1 = "123", num2 = "456" 输出: "56088" + *

+ * 说明:num1 和 num2 的长度小于110。 num1 和 num2 只包含数字 0-9。 num1 和 num2 均不以零开头,除非是数字 0 本身。 + *

+ * 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。 + * + * @see 字符串相乘 + */ + public static String multiply(String num1, String num2) { + if (num1.equals("0") || num2.equals("0")) { + return "0"; + } + + String result = "0"; + for (int i = num1.length() - 1; i >= 0; i--) { + + int carry = 0; + + StringBuilder tempBuilder = new StringBuilder(); + int value1 = num1.charAt(i) - '0'; + + for (int temp = i; temp < num1.length() - 1; temp++) { + tempBuilder.append("0"); + } + + for (int j = num2.length() - 1; j >= 0; j--) { + int value2 = num2.charAt(j) - '0'; + int value = value1 * value2 + carry; + int current = value % 10; + carry = value / 10; + tempBuilder.append(current); + } + + if (carry > 0) { + tempBuilder.append(carry); + } + + result = add(result, tempBuilder.reverse().toString()); + } + + return result; + } + + public static String add(String num1, String num2) { + StringBuilder builder = new StringBuilder(); + int carry = 0; + + for (int i = num1.length() - 1, j = num2.length() - 1; + i >= 0 || j >= 0; + i--, j--) { + + int result = carry; + if (i >= 0) { + result += num1.charAt(i) - '0'; + } + if (j >= 0) { + result += num2.charAt(j) - '0'; + } + carry = result / 10; + int current = result % 10; + builder.append(current); + } + if (carry > 0) { + builder.append(carry); + } + return builder.reverse().toString(); + } + + /** + * 给定一个字符串,逐个翻转字符串中的每个单词。 + *

+ * 示例 1: 输入: "the sky is blue" 输出: "blue is sky the" + *

+ * 示例 2: 输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 + *

+ * 示例 3: 输入: "a good example" 输出: "example good a" 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + *

+ * 说明: 无空格字符构成一个单词。 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 + *

+ * 进阶: 请选用 C 语言的用户尝试使用 O(1) 额外空间复杂度的原地解法。 + * + * @see 翻转字符串里的单词 + */ + public static String reverseWords(String s) { + StringBuilder builder = new StringBuilder(); + List list = new ArrayList<>(); + for (char c : s.toCharArray()) { + if (c != ' ') { + builder.append(c); + } else { + if (!builder.toString().equals("")) { + list.add(builder.toString()); + } + builder = new StringBuilder(); + } + } + if (!builder.toString().equals("")) { + list.add(builder.toString()); + } + + builder = new StringBuilder(); + for (int i = list.size() - 1; i >= 0; i--) { + + builder.append(list.get(i)); + if (i != 0) { + builder.append(" "); + } + } + return builder.toString(); + } + + /** + * 以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。 + *

+ * 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs + * 相对路径 + *

+ * 请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。 + *

+ * 示例 1: 输入:"/home/" 输出:"/home" 解释:注意,最后一个目录名后面没有斜杠。 + *

+ * 示例 2: 输入:"/../" 输出:"/" 解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。 + *

+ * 示例 3: 输入:"/home//foo/" 输出:"/home/foo" 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。 + *

+ * 示例 4: 输入:"/a/./b/../../c/" 输出:"/c" + *

+ * 示例 5: 输入:"/a/../../b/../c//.//" 输出:"/c" + *

+ * 示例 6: 输入:"/a//b////c/d//././/.." 输出:"/a/b/c" + * + * @see 简化路径 + */ + public static String simplifyPath(String path) { + if (path.equals("/")) { + return path; + } + + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + if (path.startsWith("/../")) { + path = path.replaceFirst("/../", "/"); + } + + path = path.replaceAll("//", "/"); + return path; + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/string/ValidAnagram.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ValidAnagram.java similarity index 91% rename from codes/algorithm/src/main/java/io/github/dunwu/algorithm/string/ValidAnagram.java rename to codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ValidAnagram.java index 6d56170..1dd91de 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/string/ValidAnagram.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/str/ValidAnagram.java @@ -1,5 +1,4 @@ -package io.github.dunwu.ds.string; - +package io.github.dunwu.algorithm.str; import java.util.HashMap; import java.util.Map; @@ -24,10 +23,20 @@ What if the inputs contain unicode characters? How would you adapt your solution to such case? */ public class ValidAnagram { + + public static void main(String[] args) { + boolean result1 = isAnagram("anagram", "nagaram"); + boolean result2 = isAnagram("rat", "car"); + boolean result3 = isAnagram("a", "ab"); + System.out.println("result:" + result1); + System.out.println("result:" + result2); + System.out.println("result:" + result3); + } + public static boolean isAnagram(String s, String t) { - if (s == null && t == null) return true; - else if (s == null || t == null) return false; - else if (s.length() != t.length()) return false; + if (s == null && t == null) { return true; } else if (s == null || t == null) { + return false; + } else if (s.length() != t.length()) { return false; } Map map = new HashMap(); for (int i = 0; i < s.length(); i++) { @@ -67,12 +76,4 @@ public static boolean isAnagram(String s, String t) { return true; } - public static void main(String[] args) { - boolean result1 = isAnagram("anagram", "nagaram"); - boolean result2 = isAnagram("rat", "car"); - boolean result3 = isAnagram("a", "ab"); - System.out.println("result:" + result1); - System.out.println("result:" + result2); - System.out.println("result:" + result3); - } } diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BTree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BTree.java new file mode 100644 index 0000000..cac20fd --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BTree.java @@ -0,0 +1,370 @@ +package io.github.dunwu.algorithm.tree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; + +/** + * 二叉树 + * + * @author Zhang Peng + * @since 2020-01-28 + */ +public class BTree> { + + /** + * 二叉树根节点 + */ + private TreeNode root; + + public BTree() { + this.root = null; + } + + public BTree(TreeNode root) { + this.root = root; + } + + public static > BTree build(T... array) { + BTree tree = new BTree<>(); + List> list = new ArrayList<>(); + + for (T value : array) { + // 创建结点,每一个结点的左结点和右结点为null + TreeNode node; + if (value == null) { + node = null; + } else { + node = new TreeNode<>(value, null, null); + } + // list中存着每一个结点 + list.add(node); + } + + // 构建二叉树 + if (list.size() > 0) { + // i表示的是根节点的索引,从0开始 + for (int i = 0; i < array.length / 2 - 1; i++) { + if (list.get(2 * i + 1) != null) { + // 左结点 + list.get(i).left = list.get(2 * i + 1); + } + if (list.get(2 * i + 2) != null) { + // 右结点 + list.get(i).right = list.get(2 * i + 2); + } + } + // 判断最后一个根结点:因为最后一个根结点可能没有右结点,所以单独拿出来处理 + int lastIndex = array.length / 2 - 1; + // 左结点 + list.get(lastIndex).left = list.get(lastIndex * 2 + 1); + // 右结点,如果数组的长度为奇数才有右结点 + if (array.length % 2 == 1) { + list.get(lastIndex).right = list.get(lastIndex * 2 + 2); + } + + tree.root = list.get(0); + } else { + tree.root = null; + } + return tree; + } + + /** + * 判断两颗二叉树是否完全一致 + *

+ * 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 + * + * @param tree1 {@link BTree} + * @param tree2 {@link BTree} + * @param 元素类型 + * @return true / false + */ + public static > boolean isEquals(final BTree tree1, final BTree tree2) { + return TreeNode.isEquals(tree1.root, tree2.root); + } + + /** + * 判断两颗二叉树的叶子节点是否相似 + * + * @param tree1 {@link BTree} + * @param tree2 {@link BTree} + * @return true / false + * @see 叶子相似的树 + */ + public static > boolean isLeafSimilar(final BTree tree1, final BTree tree2) { + List leafs1 = TreeNode.getLeafNodes(tree1.root); + List leafs2 = TreeNode.getLeafNodes(tree2.root); + return Arrays.equals(leafs1.toArray(), leafs2.toArray()); + } + + /** + * 获取叶子节点 + */ + public List getLeafNodes() { + return TreeNode.getLeafNodes(this.root); + } + + /** + * 返回二叉树的最大深度 + * + * @return 二叉树的最大深度 + */ + public int maxDepth() { + return TreeNode.maxDepth(this.root); + } + + /** + * 返回二叉树的最小深度 + * + * @return 二叉树的最小深度 + */ + public int minDepth() { + return TreeNode.minDepth(this.root); + } + + // ------------------------------------------------------------- 遍历元素 + + /** + * 将二叉树按层次遍历顺序转换为列表,即广度优先搜索(BFS) + * + * @return {@link List>} + */ + public List> levelOrderLists() { + return TreeNode.levelOrderLists(this.root); + } + + public static class TreeNode> { + + T val; + + TreeNode left; + + TreeNode right; + + public TreeNode(T val) { + this.val = val; + } + + public TreeNode(T val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } + + @Override + public String toString() { + return String.valueOf(val); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TreeNode)) return false; + TreeNode treeNode = (TreeNode) o; + return Objects.equals(val, treeNode.val) && + Objects.equals(left, treeNode.left) && + Objects.equals(right, treeNode.right); + } + + @Override + public int hashCode() { + return Objects.hash(val, left, right); + } + + public static > TreeNode build(T... values) { + + if (values == null || values.length == 0 || values[0] == null) { + return null; + } + + Queue> queue = new LinkedList<>(); + TreeNode root = new TreeNode<>(values[0]); + queue.offer(root); + + int i = 1; + while (!queue.isEmpty()) { + TreeNode current = queue.poll(); + + // 处理左子节点 + if (i < values.length && values[i] != null) { + current.left = new TreeNode(values[i]); + queue.offer(current.left); + } + i++; + + // 处理右子节点 + if (i < values.length && values[i] != null) { + current.right = new TreeNode(values[i]); + queue.offer(current.right); + } + i++; + } + + return root; + } + + public static > TreeNode find(TreeNode root, T val) { + if (root == null || Objects.equals(root.val, val)) { return root; } + TreeNode left = find(root.left, val); + if (left != null) return left; + return find(root.right, val); + } + + public static > List> toList(TreeNode root) { + List> list = new ArrayList<>(); + if (root == null) { + return list; + } + + Queue> queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + list.add(node); + if (node == null) continue; + queue.add(node.left); + queue.add(node.right); + } + + // 删除队列尾部的所有 null + int last = list.size() - 1; + while (last > 0 && list.get(last) == null) { + last--; + } + return list.subList(0, last + 1); + } + + public static > List toValueList(TreeNode root) { + List list = new ArrayList<>(); + if (root == null) { + return list; + } + + Queue> queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + if (node == null) { + list.add(null); + continue; + } else { + list.add(node.val); + } + + queue.add(node.left); + queue.add(node.right); + } + + // 删除队列尾部的所有 null + int last = list.size() - 1; + while (last > 0 && list.get(last) == null) { + last--; + } + return list.subList(0, last + 1); + } + + /** + * 判断两颗二叉树是否完全一致 + * + * @param root1 二叉树根节点,类型:{@link BTree#root} + * @param root2 二叉树根节点,类型:{@link BTree#root} + * @param 元素类型 + * @return true / false + * @see 相同的树 + */ + private static > boolean isEquals(TreeNode root1, TreeNode root2) { + if (root1 == null && root2 == null) { return true; } + if (root1 == null || root2 == null) { return false; } + if (!root1.val.equals(root2.val)) { return false; } + return isEquals(root1.left, root2.left) && isEquals(root1.right, root2.right); + } + + /** + * 获取叶子节点 + * + * @param root {@link TreeNode} + * @param 元素类型 + */ + public static > List getLeafNodes(TreeNode root) { + List res = new ArrayList<>(); + getLeafNodes(root, res); + return res; + } + + /** + * 获取叶子节点 + * + * @param root {@link TreeNode} + * @param leafs [出参]叶子节点列表{@link List} + * @param 元素类型 + */ + private static > void getLeafNodes(TreeNode root, List leafs) { + if (root == null) { return; } + if (root.left == null && root.right == null) { leafs.add(root.val); } + getLeafNodes(root.left, leafs); + getLeafNodes(root.right, leafs); + } + + /** + * 采用递归方法获取二叉树的最大深度 + * + * @param root 二叉树根节点,类型:{@link BTree#root} + * @return 二叉树的最大深度 + * @see 二叉树的最大深度 + */ + public static > int maxDepth(TreeNode root) { + if (root == null) { return 0; } + int left = maxDepth(root.left); + int right = maxDepth(root.right); + return Math.max(left, right) + 1; + } + + /** + * 采用递归方法获取二叉树的最小深度 + * + * @param root 二叉树根节点,类型:{@link BTree#root} + * @return 二叉树的最小深度 + * @see 二叉树的最小深度 + */ + public static > int minDepth(TreeNode root) { + if (root == null) { return 0; } + int left = minDepth(root.left); + int right = minDepth(root.right); + if (left == 0 || right == 0) { + return left + right + 1; + } + return Math.min(left, right) + 1; + } + + /** + * 将二叉树按层次遍历顺序转换为列表,即广度优先搜索(BFS) + * + * @return {@link List>} + * @see 二叉树的层次遍历 II + */ + public static > List> levelOrderLists(TreeNode root) { + List> lists = new ArrayList<>(); + if (root == null) { return lists; } + Queue> queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + List list = new ArrayList<>(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + list.add(node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + lists.add(list); + } + return lists; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BaseCase.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BaseCase.java new file mode 100644 index 0000000..8da17c6 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/BaseCase.java @@ -0,0 +1,25 @@ +package io.github.dunwu.algorithm.tree; + +import java.util.List; + +/** + * 基本示例 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class BaseCase { + + public static class Node extends NTree { + + public Node(int val) { + super(val); + } + + public Node(int val, List children) { + super(val, children); + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/NTree.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/NTree.java new file mode 100644 index 0000000..664e896 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/NTree.java @@ -0,0 +1,32 @@ +package io.github.dunwu.algorithm.tree; + +import java.util.LinkedList; +import java.util.List; + +/** + * N 叉树 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class NTree> { + + public int val; + public List children; + + public NTree() { + val = -1; + children = new LinkedList<>(); + } + + public NTree(int val) { + this.val = val; + this.children = new LinkedList<>(); + } + + public NTree(int val, List children) { + this.val = val; + this.children = children; + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/Node.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/Node.java new file mode 100644 index 0000000..8c295ec --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/Node.java @@ -0,0 +1,27 @@ +package io.github.dunwu.algorithm.tree; + +import java.util.LinkedList; +import java.util.List; + +// 多叉树节点 +public class Node { + + public int val; + public List children; + + public Node() { + val = -1; + children = new LinkedList<>(); + } + + public Node(int val) { + this.val = val; + this.children = new LinkedList<>(); + } + + public Node(int val, List children) { + this.val = val; + this.children = children; + } + +} \ No newline at end of file diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/State.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/State.java new file mode 100644 index 0000000..9f19b02 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/State.java @@ -0,0 +1,15 @@ +package io.github.dunwu.algorithm.tree; + +// 多叉树的层序遍历 +// 每个节点自行维护 State 类,记录深度等信息 +public class State { + + public Node node; + public int depth; + + public State(Node node, int depth) { + this.node = node; + this.depth = depth; + } + +} \ No newline at end of file diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeNode.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeNode.java new file mode 100644 index 0000000..632d59e --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/TreeNode.java @@ -0,0 +1,187 @@ +package io.github.dunwu.algorithm.tree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; + +/** + * 二叉树节点 + * + * @author Zhang Peng + * @since 2020-01-28 + */ +public class TreeNode { + + public int val; + + public TreeNode left; + + public TreeNode right; + + public TreeNode(int val) { this.val = val; } + + public TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } + + @Override + public String toString() { + return String.valueOf(val); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TreeNode)) return false; + TreeNode treeNode = (TreeNode) o; + return val == treeNode.val && + Objects.equals(left, treeNode.left) && + Objects.equals(right, treeNode.right); + } + + @Override + public int hashCode() { + return Objects.hash(val, left, right); + } + + public static String serialize(TreeNode root) { + return serialize(root, "NULL", ","); + } + + public static String serialize(TreeNode root, String nullFlag, String sepFlag) { + StringBuilder sb = new StringBuilder(); + doSerialize(root, sb, nullFlag, sepFlag); + return sb.toString(); + } + + static void doSerialize(TreeNode root, StringBuilder sb, String nullFlag, String sepFlag) { + if (root == null) { + sb.append(nullFlag).append(sepFlag); + return; + } + sb.append(root.val).append(sepFlag); + doSerialize(root.left, sb, nullFlag, sepFlag); + doSerialize(root.right, sb, nullFlag, sepFlag); + } + + public static TreeNode deserialize(String data) { + return deserialize(data, "NULL", ","); + } + + public static TreeNode deserialize(String data, String nullFlag, String sepFlag) { + LinkedList nodes = new LinkedList<>(Arrays.asList(data.split(sepFlag))); + return doDeserialize(nodes, nullFlag); + } + + static TreeNode doDeserialize(LinkedList nodes, String nullFlag) { + if (nodes.isEmpty()) return null; + + // =============== 前序遍历处理 =============== + String val = nodes.removeFirst(); + if (nullFlag.equals(val)) { return null; } + TreeNode root = new TreeNode(Integer.parseInt(val)); + // ========================================== + + root.left = doDeserialize(nodes, nullFlag); + root.right = doDeserialize(nodes, nullFlag); + return root; + } + + public static TreeNode buildTree(Integer... values) { + + if (values == null || values.length == 0 || values[0] == null) { + return null; + } + + Queue queue = new LinkedList<>(); + TreeNode root = new TreeNode(values[0]); + queue.offer(root); + + int i = 1; + while (!queue.isEmpty() && i < values.length) { + TreeNode current = queue.poll(); + + // 处理左子节点 + if (i < values.length && values[i] != null) { + current.left = new TreeNode(values[i]); + queue.offer(current.left); + } + i++; + + // 处理右子节点 + if (i < values.length && values[i] != null) { + current.right = new TreeNode(values[i]); + queue.offer(current.right); + } + i++; + } + + return root; + } + + public static TreeNode find(TreeNode root, int val) { + if (root == null || root.val == val) { return root; } + TreeNode left = find(root.left, val); + if (left != null) return left; + return find(root.right, val); + } + + public static List toList(TreeNode root) { + List list = new ArrayList<>(); + if (root == null) { + return list; + } + + Queue queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + list.add(node); + if (node == null) continue; + queue.add(node.left); + queue.add(node.right); + } + + // 删除队列尾部的所有 null + int last = list.size() - 1; + while (last > 0 && list.get(last) == null) { + last--; + } + return list.subList(0, last + 1); + } + + public static List toValueList(TreeNode root) { + List list = new ArrayList<>(); + if (root == null) { + return list; + } + + Queue queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + if (node == null) { + list.add(null); + continue; + } else { + list.add(node.val); + } + + queue.add(node.left); + queue.add(node.right); + } + + // 删除队列尾部的所有 null + int last = list.size() - 1; + while (last > 0 && list.get(last) == null) { + last--; + } + return list.subList(0, last + 1); + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/package-info.java new file mode 100644 index 0000000..e48ff20 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/package-info.java @@ -0,0 +1,7 @@ +/** + * 二叉搜索树算法 + * + * @author Zhang Peng + * @since 2020-07-07 + */ +package io.github.dunwu.algorithm.tree.bstree; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" new file mode 100644 index 0000000..c2d9a86 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" @@ -0,0 +1,52 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import org.junit.jupiter.api.Assertions; + +/** + * 96. 不同的二叉搜索树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 不同的二叉搜索树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(5, s.numTrees(3)); + Assertions.assertEquals(1, s.numTrees(1)); + } + + static class Solution { + + // 备忘录 + int[][] memo; + + // 主函数 + public int numTrees(int n) { + // 备忘录的值初始化 + memo = new int[n + 1][n + 1]; + // 计算闭区间 [1, n] 组成的 BST 个数 + return count(1, n); + } + + // 计算闭区间 [lo, hi] 组成的 BST 个数 + int count(int low, int high) { + // base case + if (low > high) { return 1; } + if (memo[low][high] != 0) { return memo[low][high]; } + + int res = 0; + for (int i = low; i <= high; i++) { + // i 的值作为根节点 root + int left = count(low, i - 1); + int right = count(i + 1, high); + // 左右子树的组合数乘积是 BST 的总数 + res += left * right; + } + memo[low][high] = res; + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\2212.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\2212.java" new file mode 100644 index 0000000..19d183b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\270\215\345\220\214\347\232\204\344\272\214\345\217\211\346\220\234\347\264\242\346\240\2212.java" @@ -0,0 +1,85 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * 95. 不同的二叉搜索树 II + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 不同的二叉搜索树2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + List output1 = s.generateTrees(3); + LinkedList> expectList1 = new LinkedList<>(); + expectList1.add(new LinkedList<>(Arrays.asList(1, null, 2, null, 3))); + expectList1.add(new LinkedList<>(Arrays.asList(1, null, 3, 2))); + expectList1.add(new LinkedList<>(Arrays.asList(2, 1, 3))); + expectList1.add(new LinkedList<>(Arrays.asList(3, 1, null, null, 2))); + expectList1.add(new LinkedList<>(Arrays.asList(3, 2, null, 1))); + Assertions.assertEquals(expectList1.size(), output1.size()); + output1.forEach(tree -> { + List expect = expectList1.poll(); + Assertions.assertArrayEquals(expect.toArray(), TreeNode.toValueList(tree).toArray()); + }); + + List output2 = s.generateTrees(1); + LinkedList> expectList2 = new LinkedList<>(); + expectList2.add(new LinkedList<>(Collections.singletonList(1))); + Assertions.assertEquals(expectList2.size(), output2.size()); + output2.forEach(tree -> { + List expect = expectList2.poll(); + Assertions.assertArrayEquals(expect.toArray(), TreeNode.toValueList(tree).toArray()); + }); + } + + static class Solution { + + // 主函数 + public List generateTrees(int n) { + if (n == 0) return new LinkedList<>(); + // 构造闭区间 [1, n] 组成的 BST + return build(1, n); + } + + // 构造闭区间 [low, high] 组成的 BST + List build(int low, int high) { + List res = new LinkedList<>(); + // base case + if (low > high) { + res.add(null); + return res; + } + + // 1、穷举 root 节点的所有可能。 + for (int i = low; i <= high; i++) { + // 2、递归构造出左右子树的所有合法 BST。 + List leftTree = build(low, i - 1); + List rightTree = build(i + 1, high); + // 3、给 root 节点穷举所有左右子树的组合。 + for (TreeNode left : leftTree) { + for (TreeNode right : rightTree) { + // i 作为根节点 root 的值 + TreeNode root = new TreeNode(i); + root.left = left; + root.right = right; + res.add(root); + } + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\345\255\220\346\240\221\347\232\204\346\234\200\345\244\247\351\224\256\345\200\274\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\345\255\220\346\240\221\347\232\204\346\234\200\345\244\247\351\224\256\345\200\274\345\222\214.java" new file mode 100644 index 0000000..42aa113 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\345\255\220\346\240\221\347\232\204\346\234\200\345\244\247\351\224\256\345\200\274\345\222\214.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 96. 不同的二叉搜索树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 二叉搜索子树的最大键值和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(20, + s.maxSumBST(TreeNode.buildTree(1, 4, 3, 2, 4, 2, 5, null, null, null, null, null, null, 4, 6))); + Assertions.assertEquals(2, s.maxSumBST(TreeNode.buildTree(4, 3, null, 1, 2))); + Assertions.assertEquals(0, s.maxSumBST(TreeNode.buildTree(-4, -2, -5))); + Assertions.assertEquals(6, s.maxSumBST(TreeNode.buildTree(2, 1, 3))); + Assertions.assertEquals(7, s.maxSumBST(TreeNode.buildTree(5, 4, 8, 3, null, 6, 3))); + } + + static class Solution { + + // 记录 BST 最大节点之和 + private int maxSum = 0; + + public int maxSumBST(TreeNode root) { + maxSum = 0; + findMaxMinSum(root); + return maxSum; + } + + // 计算以 root 为根的二叉树的最大值、最小值、节点和 + int[] findMaxMinSum(TreeNode root) { + // base case + if (root == null) { + return new int[] { + 1, Integer.MAX_VALUE, Integer.MIN_VALUE, 0 + }; + } + + // 递归计算左右子树 + int[] left = findMaxMinSum(root.left); + int[] right = findMaxMinSum(root.right); + + // ******* 后序位置 ******* + // 通过 left 和 right 推导返回值 + // 并且正确更新 maxSum 变量 + int[] res = new int[4]; + // 这个 if 在判断以 root 为根的二叉树是不是 BST + if (left[0] == 1 && right[0] == 1 && + root.val > left[2] && root.val < right[1]) { + // 以 root 为根的二叉树是 BST + res[0] = 1; + // 计算以 root 为根的这棵 BST 的最小值 + res[1] = Math.min(left[1], root.val); + // 计算以 root 为根的这棵 BST 的最大值 + res[2] = Math.max(right[2], root.val); + // 计算以 root 为根的这棵 BST 所有节点之和 + res[3] = left[3] + right[3] + root.val; + // 更新全局变量 + maxSum = Math.max(maxSum, res[3]); + } else { + // 以 root 为根的二叉树不是 BST + res[0] = 0; + // 其他的值都没必要计算了,因为用不到 + } + + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.java" new file mode 100644 index 0000000..9a6925c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\217\222\345\205\245\346\223\215\344\275\234.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 701. 二叉搜索树中的插入操作 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 二叉搜索树中的插入操作 { + + public static void main(String[] args) { + TreeNode input1 = TreeNode.buildTree(4, 2, 7, 1, 3); + TreeNode output1 = insertIntoBST(input1, 5); + Assertions.assertArrayEquals(new Integer[] { 4, 2, 7, 1, 3, 5 }, TreeNode.toValueList(output1).toArray()); + + TreeNode input2 = TreeNode.buildTree(40, 20, 60, 10, 30, 50, 70); + TreeNode output2 = insertIntoBST(input2, 25); + Assertions.assertArrayEquals(new Integer[] { 40, 20, 60, 10, 30, 50, 70, null, null, 25 }, + TreeNode.toValueList(output2).toArray()); + + TreeNode input3 = TreeNode.buildTree(4, 2, 7, 1, 3, null, null, null, null, null, null); + TreeNode output3 = insertIntoBST(input3, 5); + Assertions.assertArrayEquals(new Integer[] { 4, 2, 7, 1, 3, 5 }, + TreeNode.toValueList(output3).toArray()); + } + + public static TreeNode insertIntoBST(TreeNode root, int val) { + if (root == null) { return new TreeNode(val); } + if (root.val < val) { + root.right = (root.right == null) ? new TreeNode(val) : insertIntoBST(root.right, val); + } else { + root.left = (root.left == null) ? new TreeNode(val) : insertIntoBST(root.left, val); + } + return root; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\220\234\347\264\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\220\234\347\264\242.java" new file mode 100644 index 0000000..9650df6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\346\220\234\347\264\242.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 538. 把二叉搜索树转换为累加树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 二叉搜索树中的搜索 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode input1 = TreeNode.buildTree(4, 2, 7, 1, 3); + TreeNode output1 = s.searchBST(input1, 2); + Assertions.assertArrayEquals(new Integer[] { 2, 1, 3 }, TreeNode.toValueList(output1).toArray()); + + TreeNode output2 = s.searchBST(input1, 5); + Assertions.assertNull(output2); + } + + static class Solution { + + public TreeNode searchBST(TreeNode root, int val) { + if (root == null) { return null; } + if (root.val == val) { + return root; + } else if (root.val < val) { + return searchBST(root.right, val); + } else { + return searchBST(root.left, val); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" new file mode 100644 index 0000000..5ce506e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\254\254K\345\260\217\347\232\204\345\205\203\347\264\240.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 98. 验证二叉搜索树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 二叉搜索树中第K小的元素 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.kthSmallest(TreeNode.buildTree(3, 1, 4, null, 2), 1)); + Assertions.assertEquals(3, s.kthSmallest(TreeNode.buildTree(5, 3, 6, 2, 4, null, null, 1), 3)); + } + + static class Solution { + + private int rank = 1; + private int res = 0; + + public int kthSmallest(TreeNode root, int k) { + rank = 1; + res = 0; + dfs(root, k); + return res; + } + + void dfs(TreeNode root, int k) { + if (root == null) { return; } + dfs(root.left, k); + if (rank++ == k) { + res = root.val; + return; + } + dfs(root.right, k); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" new file mode 100644 index 0000000..5b8039e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 235. 二叉搜索树的最近公共祖先 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 二叉搜索树的最近公共祖先 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode root = TreeNode.buildTree(6, 2, 8, 0, 4, 7, 9, null, null, 3, 5); + + TreeNode node1 = s.lowestCommonAncestor(root, TreeNode.find(root, 2), TreeNode.find(root, 8)); + Assertions.assertNotNull(node1); + Assertions.assertEquals(6, node1.val); + + TreeNode node2 = s.lowestCommonAncestor(root, TreeNode.find(root, 2), TreeNode.find(root, 4)); + Assertions.assertNotNull(node2); + Assertions.assertEquals(2, node2.val); + } + + static class Solution { + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + if (root == null) return null; + if (p.val > q.val) { + // 保证 p.val <= q.val,便于后续情况讨论 + return lowestCommonAncestor(root, q, p); + } + if (root.val >= p.val && root.val <= q.val) { + // p <= root <= q + // 即 p 和 q 分别在 root 的左右子树,那么 root 就是 LCA + return root; + } + if (root.val > q.val) { + // p 和 q 都在 root 的左子树,那么 LCA 在左子树 + return lowestCommonAncestor(root.left, p, q); + } else { + // p 和 q 都在 root 的右子树,那么 LCA 在右子树 + return lowestCommonAncestor(root.right, p, q); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273.java" new file mode 100644 index 0000000..884b648 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273.java" @@ -0,0 +1,51 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 783. 二叉搜索树节点最小距离 + * + * @author Zhang Peng + * @date 2020-06-18 + */ +public class 二叉搜索树节点最小距离 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.minDiffInBST(TreeNode.buildTree(4, 2, 6, 1, 3))); + Assertions.assertEquals(1, s.minDiffInBST(TreeNode.buildTree(1, 0, 48, null, null, 12, 49))); + } + + static class Solution { + + private int pre; + private int res; + + public int minDiffInBST(TreeNode root) { + pre = -1; + res = Integer.MAX_VALUE; + dfs(root); + return res; + } + + public void dfs(TreeNode root) { + // base case + if (root == null) { return; } + + // 【前序】 + dfs(root.left); + + // 【中序】中序保证递增有序 + if (pre != -1) { + res = Math.min(res, Math.abs(root.val - pre)); + } + pre = root.val; + + // 【后序】 + dfs(root.right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221.java" new file mode 100644 index 0000000..e76ff50 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221.java" @@ -0,0 +1,47 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 538. 把二叉搜索树转换为累加树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 从二叉搜索树到更大和树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode input1 = TreeNode.buildTree(4, 1, 6, 0, 2, 5, 7, null, null, null, 3, null, null, null, 8); + TreeNode output1 = s.bstToGst(input1); + TreeNode expect1 = TreeNode.buildTree(30, 36, 21, 36, 35, 26, 15, null, null, null, 33, null, null, null, 8); + Assertions.assertEquals(expect1, output1); + + Assertions.assertEquals(TreeNode.buildTree(1, null, 1), s.bstToGst(TreeNode.buildTree(0, null, 1))); + } + + static class Solution { + + private int sum = 0; + + public TreeNode bstToGst(TreeNode root) { + sum = 0; + dfs(root); + return root; + } + + public void dfs(TreeNode root) { + if (root == null) { return; } + dfs(root.right); + sum += root.val; + root.val = sum; + // System.out.printf("%s\n", root.val); + dfs(root.left); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.java" new file mode 100644 index 0000000..e088862 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\210\240\351\231\244\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\255\347\232\204\350\212\202\347\202\271.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 701. 二叉搜索树中的插入操作 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 删除二叉搜索树中的节点 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode output1 = s.deleteNode(TreeNode.buildTree(5, 3, 6, 2, 4, null, 7), 3); + Assertions.assertEquals(TreeNode.buildTree(5, 4, 6, 2, null, null, 7), output1); + + TreeNode output2 = s.deleteNode(TreeNode.buildTree(5, 3, 6, 2, 4, null, 7), 0); + Assertions.assertEquals(TreeNode.buildTree(5, 3, 6, 2, 4, null, 7), output2); + + TreeNode output3 = s.deleteNode(TreeNode.buildTree(5, 3, 6, 2, 4, null, 7), 5); + Assertions.assertEquals(TreeNode.buildTree(6, 3, 7, 2, 4), output3); + + Assertions.assertEquals(TreeNode.buildTree(1), s.deleteNode(TreeNode.buildTree(2, 1), 2)); + + Assertions.assertNull(s.deleteNode(TreeNode.buildTree(), 0)); + Assertions.assertNull(s.deleteNode(TreeNode.buildTree(0), 0)); + } + + static class Solution { + + public TreeNode deleteNode(TreeNode root, int key) { + if (root == null) { return null; } + if (root.val == key) { + if (root.left == null) { return root.right; } + if (root.right == null) { return root.left; } + // 获得右子树最小的节点 + TreeNode minRightNode = getMin(root.right); + // 删除右子树最小的节点 + root.right = deleteNode(root.right, minRightNode.val); + // 用右子树最小的节点替换 root 节点 + minRightNode.left = root.left; + minRightNode.right = root.right; + root = minRightNode; + } else if (root.val > key) { + // 去左子树找 + root.left = deleteNode(root.left, key); + } else if (root.val < key) { + // 去右子树找 + root.right = deleteNode(root.right, key); + } + return root; + } + + public TreeNode getMin(TreeNode root) { + if (root == null) { return null; } + if (root.left != null) { return getMin(root.left); } + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" new file mode 100644 index 0000000..2ba0983 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\345\260\206\346\234\211\345\272\217\346\225\260\347\273\204\350\275\254\346\215\242\344\270\272\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 108. 将有序数组转换为二叉搜索树 + * + * @author Zhang Peng + * @since 2020-07-07 + */ +public class 将有序数组转换为二叉搜索树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(TreeNode.buildTree(0, -3, 9, -10, null, 5), + s.sortedArrayToBST(new int[] { -10, -3, 0, 5, 9 })); + Assertions.assertEquals(TreeNode.buildTree(3, 1), s.sortedArrayToBST(new int[] { 1, 3 })); + } + + static class Solution { + + public TreeNode sortedArrayToBST(int[] nums) { + return sortedArrayToBST(nums, 0, nums.length - 1); + } + + // 将闭区间 [left, right] 中的元素转化成 BST,返回根节点 + TreeNode sortedArrayToBST(int[] nums, int left, int right) { + if (left > right) { + // 区间为空 + return null; + } + // 构造根节点 + // BST 节点左小右大,中间的元素就是根节点 + int mid = (left + right) / 2; + TreeNode root = new TreeNode(nums[mid]); + // 递归构建左子树 + root.left = sortedArrayToBST(nums, left, mid - 1); + // 递归构造右子树 + root.right = sortedArrayToBST(nums, mid + 1, right); + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221.java" new file mode 100644 index 0000000..2b0c161 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 538. 把二叉搜索树转换为累加树 + * + * @author Zhang Peng + * @date 2025-10-22 + */ +public class 把二叉搜索树转换为累加树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode input1 = TreeNode.buildTree(4, 1, 6, 0, 2, 5, 7, null, null, null, 3, null, null, null, 8); + TreeNode output1 = s.convertBST(input1); + Assertions.assertArrayEquals( + new Integer[] { 30, 36, 21, 36, 35, 26, 15, null, null, null, 33, null, null, null, 8 }, + TreeNode.toValueList(output1).toArray()); + + TreeNode input2 = TreeNode.buildTree(0, null, 1); + TreeNode output2 = s.convertBST(input2); + Assertions.assertArrayEquals(new Integer[] { 1, null, 1 }, TreeNode.toValueList(output2).toArray()); + + TreeNode input3 = TreeNode.buildTree(1, 0, 2); + TreeNode output3 = s.convertBST(input3); + Assertions.assertArrayEquals(new Integer[] { 3, 3, 2 }, TreeNode.toValueList(output3).toArray()); + + TreeNode input4 = TreeNode.buildTree(3, 2, 4, 1); + TreeNode output4 = s.convertBST(input4); + Assertions.assertArrayEquals(new Integer[] { 7, 9, 4, 10 }, TreeNode.toValueList(output4).toArray()); + } + + static class Solution { + + int sum = 0; + + public TreeNode convertBST(TreeNode root) { + sum = 0; // 重置 + traverse(root); + return root; + } + + public void traverse(TreeNode root) { + if (root == null) return; + traverse(root.right); + sum += root.val; + root.val = sum; + // System.out.printf("val: %d\n", root.val); + traverse(root.left); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" new file mode 100644 index 0000000..1177d66 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/\351\252\214\350\257\201\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.java" @@ -0,0 +1,43 @@ +package io.github.dunwu.algorithm.tree.bstree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 98. 验证二叉搜索树 + * + * @author Zhang Peng + * @since 2020-07-02 + */ +public class 验证二叉搜索树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isValidBST(TreeNode.buildTree(2, 1, 3))); + Assertions.assertFalse(s.isValidBST(TreeNode.buildTree(5, 1, 4, null, null, 3, 6))); + Assertions.assertFalse(s.isValidBST(TreeNode.buildTree(2, 2, 2))); + Assertions.assertFalse(s.isValidBST(TreeNode.buildTree(5, 4, 6, null, null, 3, 7))); + Assertions.assertTrue(s.isValidBST(TreeNode.buildTree(3, 1, 5, 0, 2, 4, 6))); + } + + static class Solution { + + public boolean isValidBST(TreeNode root) { + return isValidBST(root, null, null); + } + + // 限定以 root 为根的子树节点必须满足 max.val > root.val > min.val + boolean isValidBST(TreeNode root, TreeNode min, TreeNode max) { + // base case + if (root == null) return true; + // 若 root.val 不符合 max 和 min 的限制,说明不是合法 BST + if (min != null && root.val <= min.val) return false; + if (max != null && root.val >= max.val) return false; + // 限定左子树的最大值是 root.val,右子树的最小值是 root.val + return isValidBST(root.left, min, root) + && isValidBST(root.right, root, max); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\344\270\255\346\211\200\346\234\211\350\267\235\347\246\273\344\270\272K\347\232\204\347\273\223\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\344\270\255\346\211\200\346\234\211\350\267\235\347\246\273\344\270\272K\347\232\204\347\273\223\347\202\271.java" new file mode 100644 index 0000000..c9c6a89 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\344\270\255\346\211\200\346\234\211\350\267\235\347\246\273\344\270\272K\347\232\204\347\273\223\347\202\271.java" @@ -0,0 +1,87 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +/** + * 863. 二叉树中所有距离为 K 的结点 + * + * @author Zhang Peng + * @date 2025-12-01 + */ +public class 二叉树中所有距离为K的结点 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode input = TreeNode.buildTree(1, 2, 3, 4, 5, 6, 7); + TreeNode target = TreeNode.find(input, 5); + Assertions.assertArrayEquals(new Integer[] { 7, 4, 1 }, s.distanceK(input, target, 2).toArray()); + } + + static class Solution { + + // 记录父节点:node.val -> parentNode + // 题目说了树中所有节点值都是唯一的,所以可以用 node.val 代表 TreeNode + HashMap parent = new HashMap<>(); + + public List distanceK(TreeNode root, TreeNode target, int k) { + // 遍历所有节点,记录每个节点的父节点 + traverse(root, null); + + // 开始从 target 节点施放 BFS 算法,找到距离为 k 的节点 + LinkedList q = new LinkedList<>(); + HashSet visited = new HashSet<>(); + q.offer(target); + visited.add(target.val); + // 记录离 target 的距离 + int dist = 0; + List res = new LinkedList<>(); + + while (!q.isEmpty()) { + int sz = q.size(); + for (int i = 0; i < sz; i++) { + TreeNode cur = q.poll(); + if (dist == k) { + // 找到距离起点 target 距离为 k 的节点 + res.add(cur.val); + } + // 向父节点、左右子节点扩散 + TreeNode parentNode = parent.get(cur.val); + if (parentNode != null && !visited.contains(parentNode.val)) { + visited.add(parentNode.val); + q.offer(parentNode); + } + if (cur.left != null && !visited.contains(cur.left.val)) { + visited.add(cur.left.val); + q.offer(cur.left); + } + if (cur.right != null && !visited.contains(cur.right.val)) { + visited.add(cur.right.val); + q.offer(cur.right); + } + } + // 向外扩展一圈 + dist++; + } + + return res; + } + + private void traverse(TreeNode root, TreeNode parentNode) { + if (root == null) { + return; + } + parent.put(root.val, parentNode); + // 二叉树递归框架 + traverse(root.left, root); + traverse(root.right, root); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.java" new file mode 100644 index 0000000..d27f609 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\256\214\345\205\250\346\200\247\346\243\200\351\252\214.java" @@ -0,0 +1,71 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 958. 二叉树的完全性检验 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 二叉树的完全性检验 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isCompleteTree(TreeNode.buildTree(1, 2, 3, 4, 5, 6))); + Assertions.assertTrue(s.isCompleteTree( + TreeNode.buildTree(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33))); + Assertions.assertFalse(s.isCompleteTree(TreeNode.buildTree(1, 2, 3, 4, 5, null, 7))); + Assertions.assertFalse(s.isCompleteTree(TreeNode.buildTree(1, 2, 3, 5, null, 7, 8))); + Assertions.assertFalse(s.isCompleteTree(TreeNode.buildTree(1, null, 7))); + } + + static class Solution { + + static class NodeInfo { + + public int id; + public TreeNode node; + + public NodeInfo(int id, TreeNode node) { + this.id = id; + this.node = node; + } + + } + + public boolean isCompleteTree(TreeNode root) { + + if (root == null) { return false; } + + int expect = 1; + LinkedList queue = new LinkedList<>(); + queue.offer(new NodeInfo(1, root)); + while (!queue.isEmpty()) { + int size = queue.size(); + for (int i = 0; i < size; i++) { + NodeInfo info = queue.poll(); + if (expect != info.id) { return false; } + if (info.node.left == null && info.node.right != null) { + return false; + } + if (info.node.left != null) { + queue.offer(new NodeInfo(info.id * 2, info.node.left)); + } + if (info.node.right != null) { + queue.offer(new NodeInfo(info.id * 2 + 1, info.node.right)); + } + expect++; + } + } + + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\271\263\345\235\207\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\271\263\345\235\207\345\200\274.java" new file mode 100644 index 0000000..71b0a95 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\345\271\263\345\235\207\345\200\274.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 637. 二叉树的层平均值 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 二叉树的层平均值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Double[] { 3.00000, 14.50000, 11.00000 }, + s.averageOfLevels(TreeNode.buildTree(3, 9, 20, null, null, 15, 7)).toArray()); + Assertions.assertArrayEquals(new Double[] { 3.00000, 14.50000, 11.00000 }, + s.averageOfLevels(TreeNode.buildTree(3, 9, 20, 15, 7)).toArray()); + } + + static class Solution { + + public List averageOfLevels(TreeNode root) { + if (root == null) { return new LinkedList<>(); } + + LinkedList res = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + double sum = 0.0; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + sum += node.val; + if (node.left != null) queue.offer(node.left); + if (node.right != null) queue.offer(node.right); + } + res.add(sum / size); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\206.java" new file mode 100644 index 0000000..dd133f3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\206.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 二叉树的层次遍历 + * + * @author Zhang Peng + * @since 2020-06-18 + */ +public class 二叉树的层次遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + List> expectList = new LinkedList<>(); + expectList.add(Arrays.asList(3)); + expectList.add(Arrays.asList(9, 20)); + expectList.add(Arrays.asList(15, 7)); + Assertions.assertArrayEquals(expectList.toArray(), s.levelOrder(root).toArray()); + + Solution s2 = new Solution(); + TreeNode root2 = TreeNode.buildTree(1); + List> expectList2 = new LinkedList<>(); + expectList2.add(Arrays.asList(1)); + Assertions.assertArrayEquals(expectList2.toArray(), s2.levelOrder(root2).toArray()); + + Solution s3 = new Solution(); + TreeNode root3 = TreeNode.buildTree(); + Assertions.assertArrayEquals(new LinkedList<>().toArray(), s3.levelOrder(root3).toArray()); + } + + static class Solution { + + public List> levelOrder(TreeNode root) { + + LinkedList> res = new LinkedList<>(); + if (root == null) { return res; } + + Queue queue = new LinkedList<>(); + queue.offer(root); + // while 循环控制从上向下一层层遍历 + while (!queue.isEmpty()) { + int size = queue.size(); + // 记录这一层的节点值 + List level = new LinkedList<>(); + // for 循环控制每一层从左向右遍历 + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + level.add(node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + res.add(level); + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\2062.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\2062.java" new file mode 100644 index 0000000..dae1cfc --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\261\202\346\254\241\351\201\215\345\216\2062.java" @@ -0,0 +1,73 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * 二叉树的层次遍历 II + * + * @author Zhang Peng + * @since 2020-06-18 + */ +public class 二叉树的层次遍历2 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + List> expectList = new LinkedList<>(); + expectList.add(Arrays.asList(15, 7)); + expectList.add(Arrays.asList(9, 20)); + expectList.add(Arrays.asList(3)); + Assertions.assertArrayEquals(expectList.toArray(), s.levelOrderBottom(root).toArray()); + + Solution s2 = new Solution(); + TreeNode root2 = TreeNode.buildTree(1); + List> expectList2 = new LinkedList<>(); + expectList2.add(Arrays.asList(1)); + Assertions.assertArrayEquals(expectList2.toArray(), s2.levelOrderBottom(root2).toArray()); + + Solution s3 = new Solution(); + TreeNode root3 = TreeNode.buildTree(); + Assertions.assertArrayEquals(new LinkedList<>().toArray(), s3.levelOrderBottom(root3).toArray()); + } + + static class Solution { + + public List> levelOrderBottom(TreeNode root) { + + if (root == null) { + return new ArrayList(); + } + + LinkedList queue = new LinkedList<>(); + List> result = new LinkedList<>(); + queue.add(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + List currentLevel = new LinkedList<>(); + result.add(currentLevel); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + currentLevel.add(node.val); + if (node.left != null) { + queue.offer(node.left); + } + if (node.right != null) { + queue.offer(node.right); + } + } + } + Collections.reverse(result); + return result; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\345\256\275\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\345\256\275\345\272\246.java" new file mode 100644 index 0000000..df94b8e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\345\256\275\345\272\246.java" @@ -0,0 +1,77 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 104. 二叉树的最大深度 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 二叉树的最大宽度 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(4, s.widthOfBinaryTree(TreeNode.buildTree(1, 3, 2, 5, 3, null, 9))); + Assertions.assertEquals(7, s.widthOfBinaryTree(TreeNode.buildTree(1, 3, 2, 5, null, null, 9, 6, null, 7))); + } + + static class Solution { + + public static class NodeInfo { + + public int id; + public TreeNode node; + + public NodeInfo(int id, TreeNode node) { + this.id = id; + this.node = node; + } + + @Override + public String toString() { + return "NodeInfo{" + + "id=" + id + + ", node=" + node.val + + '}'; + } + + } + + public int widthOfBinaryTree(TreeNode root) { + + if (root == null) return 0; + + int level = 0; + int maxWidth = 0; + LinkedList queue = new LinkedList<>(); + queue.offer(new NodeInfo(1, root)); + while (!queue.isEmpty()) { + level++; + int size = queue.size(); + NodeInfo left = null, right = null; + for (int i = 0; i < size; i++) { + NodeInfo info = queue.poll(); + if (info == null) { continue; } + if (left == null) { left = info; } + right = info; + if (info.node.left != null) { + queue.offer(new NodeInfo(2 * info.id, info.node.left)); + } + if (info.node.right != null) { + queue.offer(new NodeInfo(2 * info.id + 1, info.node.right)); + } + } + // System.out.printf("level: %d, left: %s, right: %s\n", level, left.toString(), right.toString()); + int width = right.id - left.id + 1; + maxWidth = Math.max(maxWidth, width); + } + return maxWidth; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..0d458c6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 103. 二叉树的锯齿形层序遍历 + * + * @author Zhang Peng + * @date 2025-10-21 + */ +public class 二叉树的锯齿形层序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + List> expectList = new LinkedList<>(); + expectList.add(Arrays.asList(3)); + expectList.add(Arrays.asList(20, 9)); + expectList.add(Arrays.asList(15, 7)); + Assertions.assertArrayEquals(expectList.toArray(), s.zigzagLevelOrder(root).toArray()); + + TreeNode root2 = TreeNode.buildTree(1, 2, 3, 4, null, null, 5); + List> expectList2 = new LinkedList<>(); + expectList2.add(Arrays.asList(1)); + expectList2.add(Arrays.asList(3, 2)); + expectList2.add(Arrays.asList(4, 5)); + Assertions.assertArrayEquals(expectList2.toArray(), s.zigzagLevelOrder(root2).toArray()); + } + + static class Solution { + + public List> zigzagLevelOrder(TreeNode root) { + List> result = new LinkedList<>(); + if (root == null) return result; + + boolean reverse = false; + LinkedList queue = new LinkedList<>(); + queue.addLast(root); + while (!queue.isEmpty()) { + int size = queue.size(); + LinkedList layer = new LinkedList<>(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.removeFirst(); + if (reverse) { + layer.addFirst(node.val); + } else { + layer.addLast(node.val); + } + if (node.left != null) queue.addLast(node.left); + if (node.right != null) queue.addLast(node.right); + } + reverse = !reverse; + result.add(layer); + } + return result; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\234\250\346\257\217\344\270\252\346\240\221\350\241\214\344\270\255\346\211\276\346\234\200\345\244\247\345\200\274.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\234\250\346\257\217\344\270\252\346\240\221\350\241\214\344\270\255\346\211\276\346\234\200\345\244\247\345\200\274.java" new file mode 100644 index 0000000..c9bdb3c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\234\250\346\257\217\344\270\252\346\240\221\350\241\214\344\270\255\346\211\276\346\234\200\345\244\247\345\200\274.java" @@ -0,0 +1,51 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 515. 在每个树行中找最大值 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 在每个树行中找最大值 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 9 }, + s.largestValues(TreeNode.buildTree(1, 3, 2, 5, 3, null, 9)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3 }, + s.largestValues(TreeNode.buildTree(1, 2, 3)).toArray()); + } + + static class Solution { + + public List largestValues(TreeNode root) { + + if (root == null) { return new LinkedList<>(); } + + LinkedList res = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + int max = Integer.MIN_VALUE; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + max = Math.max(max, node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + res.add(max); + } + + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.java" new file mode 100644 index 0000000..061e188 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\210.java" @@ -0,0 +1,64 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import cn.hutool.json.JSONUtil; +import io.github.dunwu.algorithm.tree.TreeNode; + +import java.util.LinkedList; + +/** + * 116. 填充每个节点的下一个右侧节点指针 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 填充每个节点的下一个右侧节点指针 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode treeNode = TreeNode.buildTree(1, 2, 3, 4, 5, 6, 7); + Node root = JSONUtil.toBean(JSONUtil.toJsonStr(treeNode), Node.class); + s.connect(root); + System.out.println(root); + } + + static class Solution { + + public Node connect(Node root) { + if (root == null) return root; + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + Node prev = queue.poll(); + if (prev.left != null) queue.offer(prev.left); + if (prev.right != null) queue.offer(prev.right); + for (int i = 1; i < size; i++) { + Node next = queue.poll(); + prev.next = next; + prev = next; + if (next.left != null) queue.offer(next.left); + if (next.right != null) queue.offer(next.right); + } + } + return root; + } + + } + + static class Node extends TreeNode { + + public Node next; + public Node left; + public Node right; + + public Node(int val) { + super(val); + } + + public Node(int val, TreeNode left, TreeNode right) { + super(val, left, right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\2102.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\2102.java" new file mode 100644 index 0000000..187eae0 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\241\253\345\205\205\346\257\217\344\270\252\350\212\202\347\202\271\347\232\204\344\270\213\344\270\200\344\270\252\345\217\263\344\276\247\350\212\202\347\202\271\346\214\207\351\222\2102.java" @@ -0,0 +1,68 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import cn.hutool.json.JSONUtil; +import io.github.dunwu.algorithm.tree.TreeNode; + +import java.util.LinkedList; + +/** + * 117. 填充每个节点的下一个右侧节点指针 II + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 填充每个节点的下一个右侧节点指针2 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode treeNode = TreeNode.buildTree(1, 2, 3, 4, 5, null, 7); + Node root = JSONUtil.toBean(JSONUtil.toJsonStr(treeNode), Node.class); + s.connect(root); + System.out.println(root); + } + + static class Solution { + + public Node connect(Node root) { + if (root == null) return null; + traverse(root); + return root; + } + + public void traverse(Node root) { + if (root == null) return; + LinkedList q = new LinkedList<>(); + q.offer(root); + + while (!q.isEmpty()) { + int size = q.size(); + Node prev = null; + for (int i = 0; i < size; i++) { + Node cur = q.poll(); + if (prev != null) { prev.next = cur; } + if (cur.left != null) q.offer(cur.left); + if (cur.right != null) q.offer(cur.right); + prev = cur; + } + } + } + + } + + static class Node extends TreeNode { + + public Node next; + public Node left; + public Node right; + + public Node(int val) { + super(val); + } + + public Node(int val, TreeNode left, TreeNode right) { + super(val, left, right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\245\207\345\201\266\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\245\207\345\201\266\346\240\221.java" new file mode 100644 index 0000000..8d58a76 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\245\207\345\201\266\346\240\221.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1609. 奇偶树 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 奇偶树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isEvenOddTree(TreeNode.buildTree(1, 10, 4, 3, null, 7, 9, 12, 8, 6, null, null, 2))); + Assertions.assertFalse(s.isEvenOddTree(TreeNode.buildTree(5, 4, 2, 3, 3, 7))); + Assertions.assertFalse(s.isEvenOddTree(TreeNode.buildTree(5, 9, 1, 3, 5, 7))); + Assertions.assertTrue(s.isEvenOddTree(TreeNode.buildTree(1))); + Assertions.assertTrue( + s.isEvenOddTree(TreeNode.buildTree(11, 8, 6, 1, 3, 9, 11, 30, 20, 18, 16, 12, 10, 4, 2, 17))); + } + + static class Solution { + + public boolean isEvenOddTree(TreeNode root) { + if (root == null) return false; + int level = 0; + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int size = queue.size(); + boolean odd = level % 2 != 0; + Integer lastVal = null; + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (odd) { // 奇数层 + // 奇数下标 层上的所有节点的值都是 偶 整数,从左到右按顺序 严格递减 + if (node.val % 2 != 0) { return false; } + if (lastVal != null && node.val >= lastVal) { return false; } + } else { // 偶数层 + // 偶数下标 层上的所有节点的值都是 奇 整数,从左到右按顺序 严格递增 + if (node.val % 2 == 0) { return false; } + if (lastVal != null && node.val <= lastVal) { return false; } + } + lastVal = node.val; + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + level++; + } + return true; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" new file mode 100644 index 0000000..740f4ea --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250.java" @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 919. 完全二叉树插入器 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 完全二叉树插入器 { + + public static void main(String[] args) { + CBTInserter c = new CBTInserter(TreeNode.buildTree(1, 2)); + Assertions.assertEquals(1, c.insert(3)); + Assertions.assertEquals(2, c.insert(4)); + Assertions.assertEquals(TreeNode.buildTree(1, 2, 3, 4), c.get_root()); + } + + static class CBTInserter { + + private TreeNode root; + private final LinkedList unfull; + + public CBTInserter(TreeNode root) { + this.root = root; + this.unfull = new LinkedList<>(); + + LinkedList q = new LinkedList<>(); + q.offer(this.root); + while (!q.isEmpty()) { + for (int i = 0; i < q.size(); i++) { + TreeNode node = q.poll(); + if (node.left != null) { + q.offer(node.left); + } + if (node.right != null) { + q.offer(node.right); + } + if (node.left == null || node.right == null) { + unfull.offer(node); + } + } + } + } + + public int insert(int val) { + if (root == null) { + root = new TreeNode(val); + return 0; + } + + TreeNode parent = unfull.peek(); + TreeNode node = new TreeNode(val); + if (parent.left == null) { + parent.left = node; + } else if (parent.right == null) { + parent.right = node; + unfull.poll(); + } + unfull.offer(node); + return parent.val; + } + + public TreeNode get_root() { + return this.root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\261\202\346\225\260\346\234\200\346\267\261\345\217\266\345\255\220\350\212\202\347\202\271\347\232\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\261\202\346\225\260\346\234\200\346\267\261\345\217\266\345\255\220\350\212\202\347\202\271\347\232\204\345\222\214.java" new file mode 100644 index 0000000..0992a9a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\261\202\346\225\260\346\234\200\346\267\261\345\217\266\345\255\220\350\212\202\347\202\271\347\232\204\345\222\214.java" @@ -0,0 +1,48 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1302. 层数最深叶子节点的和 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 层数最深叶子节点的和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(15, + s.deepestLeavesSum(TreeNode.buildTree(1, 2, 3, 4, 5, null, 6, 7, null, null, null, null, 8))); + Assertions.assertEquals(19, + s.deepestLeavesSum(TreeNode.buildTree(6, 7, 8, 2, 7, 1, 3, 9, null, 1, 4, null, null, null, 5))); + } + + static class Solution { + + public int deepestLeavesSum(TreeNode root) { + + if (root == null) { return 0; } + + int sum = 0; + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + sum = 0; + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + sum += node.val; + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + } + return sum; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\225.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\225.java" new file mode 100644 index 0000000..37abde6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\225.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.LinkedList; + +/** + * LCR 149. 彩灯装饰记录 I + * + * @author Zhang Peng + * @since 2025-10-28 + */ +public class 彩灯装饰记录 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(8, 17, 21, 18, null, null, 6); + Assertions.assertArrayEquals(new int[] { 8, 17, 21, 18, 6 }, s.decorateRecord(root)); + } + + static class Solution { + + public int[] decorateRecord(TreeNode root) { + ArrayList list = new ArrayList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + TreeNode node = queue.poll(); + if (node == null) { continue; } + list.add(node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + return list.stream().mapToInt(Integer::intValue).toArray(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2252.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2252.java" new file mode 100644 index 0000000..26ba27f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2252.java" @@ -0,0 +1,57 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * LCR 150. 彩灯装饰记录 II + * + * @author Zhang Peng + * @since 2025-10-28 + */ +public class 彩灯装饰记录2 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(8, 17, 21, 18, null, null, 6); + List> expectList = new ArrayList<>(); + expectList.add(Arrays.asList(8)); + expectList.add(Arrays.asList(17, 21)); + expectList.add(Arrays.asList(18, 6)); + List> output = s.decorateRecord(root); + for (int i = 0; i < expectList.size(); i++) { + Assertions.assertArrayEquals(expectList.get(i).toArray(), output.get(i).toArray()); + } + } + + static class Solution { + + public List> decorateRecord(TreeNode root) { + List> res = new ArrayList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + List list = new ArrayList<>(); + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (node == null) { continue; } + list.add(node.val); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + if (list.size() != 0) { + res.add(list); + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2253.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2253.java" new file mode 100644 index 0000000..7985e9f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\345\275\251\347\201\257\350\243\205\351\245\260\350\256\260\345\275\2253.java" @@ -0,0 +1,63 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * LCR 151. 彩灯装饰记录 III + * + * @author Zhang Peng + * @since 2025-10-28 + */ +public class 彩灯装饰记录3 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode root = TreeNode.buildTree(8, 17, 21, 18, null, null, 6); + List> expectList = new ArrayList<>(); + expectList.add(Arrays.asList(8)); + expectList.add(Arrays.asList(21, 17)); + expectList.add(Arrays.asList(18, 6)); + List> output = s.decorateRecord(root); + for (int i = 0; i < expectList.size(); i++) { + Assertions.assertArrayEquals(expectList.get(i).toArray(), output.get(i).toArray()); + } + } + + static class Solution { + + public List> decorateRecord(TreeNode root) { + List> res = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + boolean reverse = false; + while (!queue.isEmpty()) { + LinkedList list = new LinkedList<>(); + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (node == null) { continue; } + if (reverse) { + list.addFirst(node.val); + } else { + list.addLast(node.val); + } + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + if (list.size() != 0) { + res.add(list); + } + reverse = !reverse; + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\346\234\200\345\244\247\345\261\202\345\206\205\345\205\203\347\264\240\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\346\234\200\345\244\247\345\261\202\345\206\205\345\205\203\347\264\240\345\222\214.java" new file mode 100644 index 0000000..d0bd78b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/bfs/\346\234\200\345\244\247\345\261\202\345\206\205\345\205\203\347\264\240\345\222\214.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.tree.btree.bfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1161. 最大层内元素和 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 最大层内元素和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, + s.maxLevelSum(TreeNode.buildTree(1, 7, 0, 7, -8, null, null))); + Assertions.assertEquals(2, + s.maxLevelSum(TreeNode.buildTree(989, null, 10250, 98693, -89388, null, null, null, -32127))); + Assertions.assertEquals(3, + s.maxLevelSum(TreeNode.buildTree(-100, -200, -300, -20, -5, -10, null))); + } + + static class Solution { + + public int maxLevelSum(TreeNode root) { + if (root == null) { return 0; } + int depth = 1; + int max = Integer.MIN_VALUE; + int maxDepth = 1; + LinkedList queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + int sum = 0; + int size = queue.size(); + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + sum += node.val; + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + } + if (sum > max) { + max = sum; + maxDepth = depth; + } + depth++; + } + return maxDepth; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.java" new file mode 100644 index 0000000..ca687c2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\345\211\252\346\236\235.java" @@ -0,0 +1,40 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 814. 二叉树剪枝 + * + * @author Zhang Peng + * @since 2025-10-30 + */ +public class 二叉树剪枝 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(TreeNode.buildTree(1, null, 0, null, 1), + s.pruneTree(TreeNode.buildTree(1, null, 0, 0, 1))); + Assertions.assertEquals(TreeNode.buildTree(1, null, 1, null, 1), + s.pruneTree(TreeNode.buildTree(1, 0, 1, 0, 0, 0, 1))); + Assertions.assertEquals(TreeNode.buildTree(1, 1, 0, 1, 1, null, 1), + s.pruneTree(TreeNode.buildTree(1, 1, 0, 1, 1, 0, 1, 0))); + } + + static class Solution { + + public TreeNode pruneTree(TreeNode root) { + if (root == null) { return null; } + + root.left = pruneTree(root.left); + root.right = pruneTree(root.right); + + if (root.left == null && root.right == null && root.val == 0) { + return null; + } + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..0572005 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\344\270\255\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,53 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 94. 二叉树的中序遍历 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 二叉树的中序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 2 }, + s.inorderTraversal(TreeNode.buildTree(1, null, 2, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s.inorderTraversal(TreeNode.buildTree()).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, + s.inorderTraversal(TreeNode.buildTree(1)).toArray()); + } + + private static class Solution { + + List values; + + public List inorderTraversal(TreeNode root) { + values = new ArrayList<>(); + traverse(root); + return values; + } + + public void traverse(TreeNode root) { + if (root == null) return; + // 【前序】 + // System.out.printf("[node -> left]从节点 %s 进入节点 %s\n", root, root.left); + traverse(root.left); + // 【中序】 + // System.out.printf("\t[left -> node]从节点 %s 回到节点 %s\n", root.left, root); + // System.out.printf("\t[node -> right]从节点 %s 进入节点 %s\n", root, root.right); + values.add(root.val); + traverse(root.right); + // 【后序】 + // System.out.printf("\t[right -> node]从节点 %s 回到节点 %s\n", root.right, root); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..ba15417 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,79 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 144. 二叉树的前序遍历 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 二叉树的前序遍历 { + + public static void main(String[] args) { + + Solution s1 = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, + s1.preorderTraversal(TreeNode.buildTree(1, null, 2, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s1.preorderTraversal(TreeNode.buildTree()).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, + s1.preorderTraversal(TreeNode.buildTree(1)).toArray(new Integer[0])); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, + s2.preorderTraversal(TreeNode.buildTree(1, null, 2, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s2.preorderTraversal(TreeNode.buildTree()).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, + s2.preorderTraversal(TreeNode.buildTree(1)).toArray(new Integer[0])); + } + + /** + * 【分解】思路解法 + */ + private static class Solution { + + public List preorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + if (root == null) { + return res; + } + // 前序遍历的结果,root.val 在第一个 + res.add(root.val); + // 利用函数定义,后面接着左子树的前序遍历结果 + res.addAll(preorderTraversal(root.left)); + // 利用函数定义,最后接着右子树的前序遍历结果 + res.addAll(preorderTraversal(root.right)); + return res; + } + + } + + /** + * 【遍历】思路解法 + */ + private static class Solution2 { + + List res; + + public List preorderTraversal(TreeNode root) { + res = new ArrayList<>(); + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + res.add(root.val); + traverse(root.left); + traverse(root.right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..cdbcba2 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 145. 二叉树的后序遍历 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 二叉树的后序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 3, 2, 1 }, + s.postorderTraversal(TreeNode.buildTree(1, null, 2, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s.postorderTraversal(TreeNode.buildTree()).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1 }, + s.postorderTraversal(TreeNode.buildTree(1)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 4, 6, 7, 5, 2, 9, 8, 3, 1 }, + s.postorderTraversal(TreeNode.buildTree(1, 2, 3, 4, 5, null, 8, null, null, 6, 7, 9)).toArray()); + } + + private static class Solution { + + List values = null; + + public List postorderTraversal(TreeNode root) { + values = new ArrayList<>(); + traverse(root); + return values; + } + + public void traverse(TreeNode root) { + if (root == null) return; + // 【前序】 + System.out.printf("[node -> left]从节点 %s 进入节点 %s\n", root, root.left); + traverse(root.left); + // 【中序】 + System.out.printf("\t[left -> node]从节点 %s 回到节点 %s\n", root.left, root); + System.out.printf("\t[node -> right]从节点 %s 进入节点 %s\n", root, root.right); + traverse(root.right); + // 【后序】 + System.out.printf("\t[right -> node]从节点 %s 回到节点 %s\n", root.right, root); + values.add(root.val); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\235\241\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\235\241\345\272\246.java" new file mode 100644 index 0000000..2d5500b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\344\272\214\345\217\211\346\240\221\347\232\204\345\235\241\345\272\246.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 110. 平衡二叉树 + * + * @author Zhang Peng + * @since 2025-10-30 + */ +public class 二叉树的坡度 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(1, s.findTilt(TreeNode.buildTree(1, 2, 3))); + Assertions.assertEquals(15, s.findTilt(TreeNode.buildTree(4, 2, 9, 3, 5, null, 7))); + Assertions.assertEquals(9, s.findTilt(TreeNode.buildTree(21, 7, 14, 1, 1, 2, 2, 3, 3))); + } + + static class Solution { + + int res = 0; + + public int findTilt(TreeNode root) { + res = 0; + sum(root); + return res; + } + + public int sum(TreeNode root) { + if (root == null) { return 0; } + int left = sum(root.left); + int right = sum(root.right); + res += Math.abs(left - right); + return left + right + root.val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\207\272\347\216\260\346\254\241\346\225\260\346\234\200\345\244\232\347\232\204\345\255\220\346\240\221\345\205\203\347\264\240\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\207\272\347\216\260\346\254\241\346\225\260\346\234\200\345\244\232\347\232\204\345\255\220\346\240\221\345\205\203\347\264\240\345\222\214.java" new file mode 100644 index 0000000..550c275 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\207\272\347\216\260\346\254\241\346\225\260\346\234\200\345\244\232\347\232\204\345\255\220\346\240\221\345\205\203\347\264\240\345\222\214.java" @@ -0,0 +1,65 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 508. 出现次数最多的子树元素和 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 出现次数最多的子树元素和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertArrayEquals(new int[] { 2, -3, 4 }, s.findFrequentTreeSum(TreeNode.buildTree(5, 2, -3))); + Assertions.assertArrayEquals(new int[] { 2 }, s.findFrequentTreeSum(TreeNode.buildTree(5, 2, -5))); + } + + static class Solution { + + private Map map; + + public int[] findFrequentTreeSum(TreeNode root) { + map = new HashMap<>(); + sum(root); + + int max = Integer.MIN_VALUE; + List list = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + Integer k = entry.getKey(); + Integer v = entry.getValue(); + if (v > max) { + max = v; + list.clear(); + list.add(k); + } else if (v == max) { + list.add(k); + } + } + + int[] res = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + res[i] = list.get(i); + } + return res; + } + + public int sum(TreeNode root) { + if (root == null) { return 0; } + int left = sum(root.left); + int right = sum(root.right); + int sum = left + right + root.val; + map.put(sum, map.getOrDefault(sum, 0) + 1); + return sum; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271.java" new file mode 100644 index 0000000..f273bae --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\210\240\351\231\244\347\273\231\345\256\232\345\200\274\347\232\204\345\217\266\345\255\220\350\212\202\347\202\271.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 814. 二叉树剪枝 + * + * @author Zhang Peng + * @since 2025-10-30 + */ +public class 删除给定值的叶子节点 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(TreeNode.buildTree(1, null, 3, null, 4), + s.removeLeafNodes(TreeNode.buildTree(1, 2, 3, 2, null, 2, 4), 2)); + Assertions.assertEquals(TreeNode.buildTree(1, 3, null, null, 2), + s.removeLeafNodes(TreeNode.buildTree(1, 3, 3, 3, 2), 3)); + Assertions.assertEquals(TreeNode.buildTree(1), + s.removeLeafNodes(TreeNode.buildTree(1, 2, null, 2, null, 2), 2)); + } + + static class Solution { + + public TreeNode removeLeafNodes(TreeNode root, int target) { + if (root == null) { return null; } + root.left = removeLeafNodes(root.left, target); + root.right = removeLeafNodes(root.right, target); + if (root.left == null && root.right == null && root.val == target) { + return null; + } + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" new file mode 100644 index 0000000..e1463ce --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" @@ -0,0 +1,55 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * LCR 149. 彩灯装饰记录 I + * + * @author Zhang Peng + * @since 2025-10-28 + */ +public class 叶子相似的树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.leafSimilar(TreeNode.buildTree(3, 5, 1, 6, 2, 9, 8, null, null, 7, 4), + TreeNode.buildTree(3, 5, 1, 6, 7, 4, 2, null, null, null, null, null, null, 9, 8))); + } + + static class Solution { + + public boolean leafSimilar(TreeNode root1, TreeNode root2) { + List root1Leafs = new ArrayList<>(); + List root2Leafs = new ArrayList<>(); + getLeafs(root1, root1Leafs); + getLeafs(root2, root2Leafs); + if (root1Leafs.size() != root2Leafs.size()) { + return false; + } + for (int i = 0; i < root1Leafs.size(); i++) { + if (!Objects.equals(root1Leafs.get(i), root2Leafs.get(i))) { + return false; + } + } + return true; + } + + public void getLeafs(TreeNode root, List leafs) { + if (root == null) { + return; + } + if (root.left == null && root.right == null) { + leafs.add(root.val); + } + getLeafs(root.left, leafs); + getLeafs(root.right, leafs); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..b67c5f4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/dfs/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,44 @@ +package io.github.dunwu.algorithm.tree.btree.dfs; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 110. 平衡二叉树 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 平衡二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isBalanced(TreeNode.buildTree(3, 9, 20, null, null, 15, 7))); + Assertions.assertFalse(s.isBalanced(TreeNode.buildTree(1, 2, 2, 3, 3, null, null, 4, 4))); + Assertions.assertTrue(s.isBalanced(TreeNode.buildTree())); + } + + static class Solution { + + boolean isOk = true; + + public boolean isBalanced(TreeNode root) { + isOk = true; + maxDepth(root); + return isOk; + } + + public int maxDepth(TreeNode root) { + if (root == null) { return 0; } + if (root.left == null && root.right == null) { return 1; } + int leftDepth = maxDepth(root.left); + int rightDepth = maxDepth(root.right); + if (Math.abs(leftDepth - rightDepth) > 1) { + isOk = false; + } + return Math.max(leftDepth, rightDepth) + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.java" new file mode 100644 index 0000000..820c810 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\350\267\257\345\276\204\345\222\214.java" @@ -0,0 +1,41 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 124. 二叉树中的最大路径和 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 二叉树中的最大路径和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.maxPathSum(TreeNode.buildTree(1, 2, 3))); + Assertions.assertEquals(42, s.maxPathSum(TreeNode.buildTree(-10, 9, 20, null, null, 15, 7))); + } + + static class Solution { + + int res = Integer.MIN_VALUE; + + public int maxPathSum(TreeNode root) { + if (root == null) { return 0; } + oneSideMax(root); + return res; + } + + int oneSideMax(TreeNode root) { + if (root == null) { return 0; } + int left = Math.max(oneSideMax(root.left), 0); + int right = Math.max(oneSideMax(root.right), 0); + int pathMaxSum = root.val + left + right; + res = Math.max(res, pathMaxSum); + return Math.max(left, right) + root.val; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..b0b70bf --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\344\270\255\345\272\217\344\270\216\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 106. + * 从中序与后序遍历序列构造二叉树 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 从中序与后序遍历序列构造二叉树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode output1 = s.buildTree(new int[] { 9, 3, 15, 20, 7 }, new int[] { 9, 15, 7, 20, 3 }); + Assertions.assertEquals(TreeNode.buildTree(3, 9, 20, null, null, 15, 7), output1); + + TreeNode output2 = s.buildTree(new int[] { -1 }, new int[] { -1 }); + Assertions.assertEquals(TreeNode.buildTree(-1), output2); + } + + static class Solution { + + Map map = null; + + public TreeNode buildTree(int[] inorder, int[] postorder) { + if (inorder == null || postorder == null) { return null; } + map = new HashMap<>(inorder.length); + for (int i = 0; i < inorder.length; i++) { + map.put(inorder[i], i); + } + return build(inorder, 0, inorder.length - 1, + postorder, 0, postorder.length - 1); + } + + public TreeNode build(int[] inorder, int inLow, int inHigh, + int[] postorder, int postLow, int postHigh) { + if (postLow > postHigh) { return null; } + int inMid = map.get(postorder[postHigh]); + int leftLen = inMid - inLow; + TreeNode root = new TreeNode(postorder[postHigh]); + root.left = build(inorder, inLow, inMid - 1, + postorder, postLow, postLow + leftLen - 1); + root.right = build(inorder, inMid + 1, inHigh, + postorder, postLow + leftLen, postHigh - 1); + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..af00d0e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\344\273\216\345\211\215\345\272\217\344\270\216\344\270\255\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,58 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 105. + * 从前序与中序遍历序列构造二叉树 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 从前序与中序遍历序列构造二叉树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode output1 = s.buildTree(new int[] { 3, 9, 20, 15, 7 }, new int[] { 9, 3, 15, 20, 7 }); + Assertions.assertEquals(TreeNode.buildTree(3, 9, 20, null, null, 15, 7), output1); + + TreeNode output2 = s.buildTree(new int[] { -1 }, new int[] { -1 }); + Assertions.assertEquals(TreeNode.buildTree(-1), output2); + } + + static class Solution { + + Map map = null; + + public TreeNode buildTree(int[] preorder, int[] inorder) { + if (inorder == null || preorder == null) { return null; } + map = new HashMap<>(inorder.length); + for (int i = 0; i < inorder.length; i++) { + map.put(inorder[i], i); + } + return build(preorder, 0, preorder.length - 1, + inorder, 0, inorder.length - 1); + } + + public TreeNode build(int[] preorder, int preLow, int preHigh, + int[] inorder, int inLow, int inHigh) { + if (preLow > preHigh) { return null; } + int inMid = map.get(preorder[preLow]); + int leftLen = inMid - inLow; + TreeNode root = new TreeNode(preorder[preLow]); + root.left = build(preorder, preLow + 1, preLow + leftLen, + inorder, inLow, inMid - 1); + root.right = build(preorder, preLow + leftLen + 1, preHigh, + inorder, inMid + 1, inHigh); + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\210\240\347\202\271\346\210\220\346\236\227.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\210\240\347\202\271\346\210\220\346\236\227.java" new file mode 100644 index 0000000..c3a3959 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\210\240\347\202\271\346\210\220\346\236\227.java" @@ -0,0 +1,67 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 1110. 删点成林 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 删点成林 { + + public static void main(String[] args) { + Solution s = new Solution(); + + List output = s.delNodes(TreeNode.buildTree(1, 2, 3, 4, 5, 6, 7), new int[] { 3, 5 }); + List expect = new ArrayList<>(); + expect.add(TreeNode.buildTree(1, 2, null, 4)); + expect.add(TreeNode.buildTree(6)); + expect.add(TreeNode.buildTree(7)); + for (int i = 0; i < output.size(); i++) { + Assertions.assertEquals(expect.get(i), output.get(i)); + } + + List output2 = s.delNodes(TreeNode.buildTree(1, 2, 4, null, 3), new int[] { 3 }); + List expect2 = new ArrayList<>(); + expect2.add(TreeNode.buildTree(1, 2, 4)); + for (int i = 0; i < output2.size(); i++) { + Assertions.assertEquals(expect2.get(i), output2.get(i)); + } + } + + static class Solution { + + private List res; + + public List delNodes(TreeNode root, int[] to_delete) { + res = new ArrayList<>(); + Set set = new HashSet<>(); + for (int val : to_delete) { + set.add(val); + } + delNodes(root, set, false); + return res; + } + + public TreeNode delNodes(TreeNode root, Set set, boolean hasParent) { + if (root == null) { return null; } + boolean deleted = set.contains(root.val); + if (!deleted && !hasParent) { + res.add(root); + } + + root.left = delNodes(root.left, set, !deleted); + root.right = delNodes(root.right, set, !deleted); + return deleted ? null : root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..0daa7f4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\345\257\271\347\247\260\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,38 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 101. 对称二叉树 + * + * @author Zhang Peng + * @date 2020-01-28 + */ +public class 对称二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isSymmetric(TreeNode.buildTree(1, 2, 2, 3, 4, 4, 3))); + Assertions.assertFalse(s.isSymmetric(TreeNode.buildTree(1, 2, 2, null, 3, null, 3))); + } + + static class Solution { + + public boolean isSymmetric(TreeNode root) { + if (root == null) { return true; } + return isSymmetric(root.left, root.right); + } + + boolean isSymmetric(TreeNode left, TreeNode right) { + if (left == null && right == null) { return true; } + if (left == null || right == null) { return false; } + // 两个根节点需要相同 + if (left.val != right.val) { return false; } + // 左右子树也需要镜像对称 + return isSymmetric(left.left, right.right) && isSymmetric(left.right, right.left); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\347\234\237\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\347\234\237\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..e943be3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\211\200\346\234\211\345\217\257\350\203\275\347\232\204\347\234\237\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,77 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; + +import java.util.LinkedList; +import java.util.List; + +/** + * 894. 所有可能的真二叉树 + * + * @author Zhang Peng + * @date 2025-10-21 + */ +public class 所有可能的真二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + List trees = s.allPossibleFBT(7); + trees.forEach(tree -> { + System.out.println(TreeNode.serialize(tree)); + }); + } + + static class Solution { + + // 备忘录,记录 n 个节点能够组合成的所有可能二叉树 + List[] memo; + + public List allPossibleFBT(int n) { + if (n % 2 == 0) { + // 题目描述的满二叉树不可能是偶数个节点 + return new LinkedList<>(); + } + memo = new LinkedList[n + 1]; + return build(n); + } + + // 定义:输入一个 n,生成节点树为 n 的所有可能的满二叉树 + public List build(int n) { + List res = new LinkedList<>(); + // base case + if (n == 1) { + res.add(new TreeNode(0)); + return res; + } + if (memo[n] != null) { + // 避免冗余计算 + return memo[n]; + } + + // 递归生成所有符合条件的左右子树 + for (int i = 1; i < n; i += 2) { + int j = n - i - 1; + // 利用函数定义,生成左右子树 + List leftSubTrees = build(i); + List rightSubTrees = build(j); + // 左右子树的不同排列也能构成不同的二叉树 + for (TreeNode left : leftSubTrees) { + for (TreeNode right : rightSubTrees) { + // 生成根节点 + TreeNode root = new TreeNode(0); + // 组装出一种可能的二叉树形状 + root.left = left; + root.right = right; + // 加入结果列表 + res.add(root); + } + } + } + // 存入备忘录 + memo[n] = res; + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\2212.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\2212.java" new file mode 100644 index 0000000..4cac6df --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\2212.java" @@ -0,0 +1,48 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 998. 最大二叉树 II + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 最大二叉树2 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + Assertions.assertEquals(TreeNode.buildTree(5, 4, null, 1, 3, null, null, 2), + s.insertIntoMaxTree(TreeNode.buildTree(4, 1, 3, null, null, 2), 5)); + + Assertions.assertEquals(TreeNode.buildTree(5, 2, 4, null, 1, null, 3), + s.insertIntoMaxTree(TreeNode.buildTree(5, 2, 4, null, 1), 3)); + + Assertions.assertEquals(TreeNode.buildTree(5, 2, 4, null, 1, 3), + s.insertIntoMaxTree(TreeNode.buildTree(5, 2, 3, null, 1), 4)); + } + + static class Solution { + + public TreeNode insertIntoMaxTree(TreeNode root, int val) { + if (root == null) { return new TreeNode(val); } + if (root.val < val) { + // 如果 val 是整棵树最大的,那么原来的这棵树应该是 val 节点的左子树, + // 因为 val 节点是接在原始数组 a 的最后一个元素 + TreeNode node = new TreeNode(val); + node.left = root; + return node; + } else { + // 如果 val 不是最大的,那么就应该在右子树上, + // 因为 val 节点是接在原始数组 a 的最后一个元素 + root.right = insertIntoMaxTree(root.right, val); + return root; + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\240\271\346\215\256\345\211\215\345\272\217\345\222\214\345\220\216\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\240\271\346\215\256\345\211\215\345\272\217\345\222\214\345\220\216\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..5bb6c8f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\346\240\271\346\215\256\345\211\215\345\272\217\345\222\214\345\220\216\345\272\217\351\201\215\345\216\206\346\236\204\351\200\240\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,59 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.HashMap; +import java.util.Map; + +/** + * 106. + * 从中序与后序遍历序列构造二叉树 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 根据前序和后序遍历构造二叉树 { + + public static void main(String[] args) { + + Solution s = new Solution(); + TreeNode output1 = s.constructFromPrePost(new int[] { 1, 2, 4, 5, 3, 6, 7 }, + new int[] { 4, 5, 2, 6, 7, 3, 1 }); + Assertions.assertEquals(TreeNode.buildTree(1, 2, 3, 4, 5, 6, 7), output1); + + TreeNode output2 = s.constructFromPrePost(new int[] { 1 }, new int[] { 1 }); + Assertions.assertEquals(TreeNode.buildTree(1), output2); + } + + static class Solution { + + Map map = new HashMap<>(); + + public TreeNode constructFromPrePost(int[] preorder, int[] postorder) { + if (preorder.length == 0 || postorder.length == 0) { return null; } + for (int i = 0; i < postorder.length; i++) { + map.put(postorder[i], i); + } + return build(preorder, 0, preorder.length - 1, + postorder, 0, postorder.length - 1); + } + + public TreeNode build(int[] preorder, int preLow, int preHigh, + int[] postorder, int postLow, int postHigh) { + if (preLow > preHigh) { return null; } + if (preLow == preHigh) { return new TreeNode(preorder[preLow]); } + int leftRootVal = preorder[preLow + 1]; + int leftPostHigh = map.get(leftRootVal); + int leftLen = leftPostHigh - postLow + 1; + TreeNode root = new TreeNode(preorder[preLow]); + root.left = build(preorder, preLow + 1, preLow + leftLen, + postorder, postLow, leftPostHigh); + root.right = build(preorder, preLow + leftLen + 1, preHigh, + postorder, leftPostHigh + 1, postHigh - 1); + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\233\270\345\220\214\347\232\204\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\233\270\345\220\214\347\232\204\346\240\221.java" new file mode 100644 index 0000000..04205a8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\233\270\345\220\214\347\232\204\346\240\221.java" @@ -0,0 +1,32 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 100. 相同的树 + * + * @author Zhang Peng + * @date 2020-01-28 + */ +public class 相同的树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isSameTree(TreeNode.buildTree(1, 2, 3), TreeNode.buildTree(1, 2, 3))); + Assertions.assertFalse(s.isSameTree(TreeNode.buildTree(1, 2), TreeNode.buildTree(1, 2, 3))); + Assertions.assertFalse(s.isSameTree(TreeNode.buildTree(1, 2, 1), TreeNode.buildTree(1, 1, 2))); + } + + static class Solution { + + public boolean isSameTree(TreeNode p, TreeNode q) { + if (p == null && q == null) { return true; } + if (p == null || q == null) { return false; } + if (p.val != q.val) { return false; } + return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\277\273\350\275\254\347\255\211\344\273\267\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\277\273\350\275\254\347\255\211\344\273\267\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..f3d1c1f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\347\277\273\350\275\254\347\255\211\344\273\267\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,39 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 951. 翻转等价二叉树 + * + * @author Zhang Peng + * @date 2025-10-29 + */ +public class 翻转等价二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode p = TreeNode.buildTree(1, 2, 3, 4, 5, 6, null, null, null, 7, 8); + TreeNode q = TreeNode.buildTree(1, 3, 2, null, 6, 4, 5, null, null, null, null, 8, 7); + Assertions.assertTrue(s.flipEquiv(p, q)); + Assertions.assertTrue(s.flipEquiv(TreeNode.buildTree(), TreeNode.buildTree())); + Assertions.assertFalse(s.flipEquiv(TreeNode.buildTree(), TreeNode.buildTree(1))); + } + + static class Solution { + + // 定义:输入两棵二叉树,判断这两棵二叉树是否是翻转等价的 + public boolean flipEquiv(TreeNode root1, TreeNode root2) { + // 判断 root1 和 root2 两个节点是否能够匹配 + if (root1 == null && root2 == null) { return true; } + if (root1 == null || root2 == null) { return false; } + if (root1.val != root2.val) { return false; } + // 根据函数定义,判断子树是否能够匹配 + // 不翻转、翻转两种情况满足一种即可算是匹配 + return (flipEquiv(root1.left, root2.left) && flipEquiv(root1.right, root2.right)) // 不翻转子树 + || (flipEquiv(root1.left, root2.right) && flipEquiv(root1.right, root2.left)); // 翻转子树 + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\351\252\214\350\257\201\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\345\272\217\345\210\227\345\214\226.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\351\252\214\350\257\201\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\345\272\217\345\210\227\345\214\226.java" new file mode 100644 index 0000000..6165bf4 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/divide/\351\252\214\350\257\201\344\272\214\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\345\272\217\345\210\227\345\214\226.java" @@ -0,0 +1,72 @@ +package io.github.dunwu.algorithm.tree.btree.divide; + +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 331. 验证二叉树的前序序列化 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 验证二叉树的前序序列化 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertTrue(s.isValidSerialization("9,3,4,#,#,1,#,#,2,#,6,#,#")); + Assertions.assertFalse(s.isValidSerialization("1,#")); + Assertions.assertFalse(s.isValidSerialization("9,#,#,1")); + + Solution2 s2 = new Solution2(); + Assertions.assertTrue(s2.isValidSerialization("9,3,4,#,#,1,#,#,2,#,6,#,#")); + Assertions.assertFalse(s2.isValidSerialization("1,#")); + Assertions.assertFalse(s2.isValidSerialization("9,#,#,1")); + } + + static class Solution { + + /** + * 参考题解:https://leetcode.cn/problems/verify-preorder-serialization-of-a-binary-tree/solutions/651132/pai-an-jiao-jue-de-liang-chong-jie-fa-zh-66nt + */ + public boolean isValidSerialization(String preorder) { + LinkedList stack = new LinkedList<>(); + for (String s : preorder.split(",")) { + stack.push(s); + while (stack.size() >= 3 + && stack.get(0).equals("#") + && stack.get(1).equals("#") + && !stack.get(2).equals("#")) { + stack.pop(); + stack.pop(); + stack.pop(); + stack.push("#"); + } + } + return stack.size() == 1 && stack.pop().equals("#"); + } + + } + + static class Solution2 { + + /** + * 参考题解:https://leetcode.cn/problems/verify-preorder-serialization-of-a-binary-tree/solutions/651132/pai-an-jiao-jue-de-liang-chong-jie-fa-zh-66nt + */ + public boolean isValidSerialization(String preorder) { + int diff = 1; + for (String s : preorder.split(",")) { + diff -= 1; + if (diff < 0) { + return false; + } + if (!s.equals("#")) { + diff += 2; + } + } + return diff == 0; + } + + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/package-info.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/package-info.java new file mode 100644 index 0000000..8d7a7e9 --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/package-info.java @@ -0,0 +1,7 @@ +/** + * 二叉树算法 + * + * @author Zhang Peng + * @since 2020-06-18 + */ +package io.github.dunwu.algorithm.tree.btree; diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\344\274\252\345\233\236\346\226\207\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\344\274\252\345\233\236\346\226\207\350\267\257\345\276\204.java" new file mode 100644 index 0000000..908fd91 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\344\274\252\345\233\236\346\226\207\350\267\257\345\276\204.java" @@ -0,0 +1,68 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 1457. 二叉树中的伪回文路径 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 二叉树中的伪回文路径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(2, + s.pseudoPalindromicPaths(TreeNode.buildTree(2, 3, 1, 3, 1, null, 1))); + Assertions.assertEquals(1, + s.pseudoPalindromicPaths(TreeNode.buildTree(2, 1, 1, 1, 3, null, null, null, null, null, 1))); + Assertions.assertEquals(1, + s.pseudoPalindromicPaths(TreeNode.buildTree(9))); + } + + static class Solution { + + int res = 0; + // 计数数组,题目说了 1 <= root.val <= 9 + int[] count; + + public int pseudoPalindromicPaths(TreeNode root) { + res = 0; + count = new int[10]; + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + + // 选择 + if (root.left == null && root.right == null) { + count[root.val]++; + int odd = 0; + for (int cnt : count) { + if (cnt % 2 != 0) { + odd++; + } + } + if (odd <= 1) { + res++; + } + count[root.val]--; + return; + } + + // 选择 + count[root.val]++; + + traverse(root.left); + traverse(root.right); + + // 取消选择 + count[root.val]--; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\350\247\206\345\233\276.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\350\247\206\345\233\276.java" new file mode 100644 index 0000000..58ab169 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\345\217\263\350\247\206\345\233\276.java" @@ -0,0 +1,96 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * 求根节点到叶节点数字之和 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 二叉树的右视图 { + + public static void main(String[] args) { + + Solution s = new Solution(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 4 }, + s.rightSideView(TreeNode.buildTree(1, 2, 3, null, 5, null, 4)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 4, 5 }, + s.rightSideView(TreeNode.buildTree(1, 2, 3, 4, null, null, null, 5)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3 }, + s.rightSideView(TreeNode.buildTree(1, null, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s.rightSideView(TreeNode.buildTree()).toArray()); + + Solution2 s2 = new Solution2(); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 4 }, + s2.rightSideView(TreeNode.buildTree(1, 2, 3, null, 5, null, 4)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 4, 5 }, + s2.rightSideView(TreeNode.buildTree(1, 2, 3, 4, null, null, null, 5)).toArray()); + Assertions.assertArrayEquals(new Integer[] { 1, 3 }, + s2.rightSideView(TreeNode.buildTree(1, null, 3)).toArray()); + Assertions.assertArrayEquals(new Integer[] {}, + s2.rightSideView(TreeNode.buildTree()).toArray()); + + } + + // 【层序遍历】思路 + static class Solution { + + LinkedList res = null; + + public List rightSideView(TreeNode root) { + if (root == null) { return new LinkedList<>(); } + + res = new LinkedList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + // 每层将最后一个元素加入结果集 + for (int i = 0; i < size; i++) { + TreeNode node = queue.poll(); + if (node.left != null) { queue.offer(node.left); } + if (node.right != null) { queue.offer(node.right); } + if (i == size - 1) { + res.add(node.val); + } + } + } + return res; + } + + } + + // 【遍历递归】思路 + static class Solution2 { + + int depth = 0; + LinkedHashMap map = null; + + public List rightSideView(TreeNode root) { + map = new LinkedHashMap<>(); + traverse(root); + return new LinkedList<>(map.values()); + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + depth++; + if (!map.containsKey(depth)) { + map.put(depth, root.val); + } + traverse(root.right); + traverse(root.left); + depth--; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" new file mode 100644 index 0000000..8da62a1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\272\214\345\217\211\346\240\221\347\232\204\346\211\200\346\234\211\350\267\257\345\276\204.java" @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * 二叉树的所有路径 + * + * @author Zhang Peng + * @since 2025-08-15 + */ +public class 二叉树的所有路径 { + + public static void main(String[] args) { + + Solution s = new Solution(); + Assertions.assertArrayEquals(Arrays.asList("1->2->5", "1->3").toArray(), + s.binaryTreePaths(TreeNode.buildTree(1, 2, 3, 5)).toArray()); + Assertions.assertArrayEquals(Collections.singletonList("1").toArray(), + s.binaryTreePaths(TreeNode.buildTree(1)).toArray()); + } + + static class Solution { + + LinkedList res = null; + LinkedList paths = null; + + public List binaryTreePaths(TreeNode root) { + res = new LinkedList<>(); + paths = new LinkedList<>(); + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + + // 选择 + paths.addLast(String.valueOf(root.val)); + if (root.left == null && root.right == null) { + res.addLast(String.join("->", paths)); + // System.out.printf("res: %s\n", JSONUtil.toJsonStr(res)); + } + + // 【前序】 + // System.out.format("root: %s -> root.left: %s\n", root.val, root.left.val); + traverse(root.left); + // 【中序】 + // System.out.format("root.left: %s -> root: %s\n", root.left.val, root.val); + // System.out.format("root: %s -> root.right: %s\n", root.val, root.right.val); + traverse(root.right); + // 【后序】 + // System.out.format("root.right: %s -> root: %s\n", root.right.val, root.val); + + // 取消选择 + paths.removeLast(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\345\217\266\347\273\223\347\202\271\345\274\200\345\247\213\347\232\204\346\234\200\345\260\217\345\255\227\347\254\246\344\270\262.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\345\217\266\347\273\223\347\202\271\345\274\200\345\247\213\347\232\204\346\234\200\345\260\217\345\255\227\347\254\246\344\270\262.java" new file mode 100644 index 0000000..cabcee9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\345\217\266\347\273\223\347\202\271\345\274\200\345\247\213\347\232\204\346\234\200\345\260\217\345\255\227\347\254\246\344\270\262.java" @@ -0,0 +1,71 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 988. 从叶结点开始的最小字符串 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 从叶结点开始的最小字符串 { + + public static void main(String[] args) { + + Solution s = new Solution(); + Assertions.assertEquals("dba", + s.smallestFromLeaf(TreeNode.buildTree(0, 1, 2, 3, 4, 3, 4))); + Assertions.assertEquals("adz", + s.smallestFromLeaf(TreeNode.buildTree(25, 1, 3, 1, 3, 0, 2))); + Assertions.assertEquals("abc", + s.smallestFromLeaf(TreeNode.buildTree(2, 2, 1, null, 1, 0, null, 0))); + } + + static class Solution { + + String res = null; + LinkedList nodes = null; + + public String smallestFromLeaf(TreeNode root) { + res = null; + nodes = new LinkedList<>(); + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + // 校验 + if (root == null) { return; } + + // 选择 + nodes.addLast(root.val); + if (root.left == null && root.right == null) { + String str = toString(nodes); + // System.out.printf("\tstr: %s\n", str); + if (res == null || str.compareTo(res) < 0) { + res = str; + } + } + + traverse(root.left); + traverse(root.right); + + // 取消选择 + nodes.removeLast(); + } + + public String toString(LinkedList nodes) { + StringBuilder sb = new StringBuilder(); + for (int node : nodes) { + char c = (char) (node + 'a'); + sb.insert(0, c); + } + return sb.toString(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\346\240\271\345\210\260\345\217\266\347\232\204\344\272\214\350\277\233\345\210\266\346\225\260\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\346\240\271\345\210\260\345\217\266\347\232\204\344\272\214\350\277\233\345\210\266\346\225\260\344\271\213\345\222\214.java" new file mode 100644 index 0000000..6b45eec --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\344\273\216\346\240\271\345\210\260\345\217\266\347\232\204\344\272\214\350\277\233\345\210\266\346\225\260\344\271\213\345\222\214.java" @@ -0,0 +1,63 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 1022. 从根到叶的二进制数之和 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 从根到叶的二进制数之和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(22, s.sumRootToLeaf(TreeNode.buildTree(1, 0, 1, 0, 1, 0, 1))); + Assertions.assertEquals(0, s.sumRootToLeaf(TreeNode.buildTree(0))); + } + + static class Solution { + + int sum = 0; + LinkedList nodes; + + public int sumRootToLeaf(TreeNode root) { + sum = 0; + nodes = new LinkedList<>(); + traverse(root); + return sum; + } + + public void traverse(TreeNode node) { + // 校验 + if (node == null) return; + + // 选择 + nodes.addLast(node.val); + if (node.left == null && node.right == null) { + Integer num = toNum(nodes); + System.out.printf("\tnum: %d\n", num); + sum += num; + } + + traverse(node.left); + traverse(node.right); + + // 取消选择 + nodes.removeLast(); + } + + Integer toNum(LinkedList nodes) { + int num = 0; + for (int node : nodes) { + num = num * 2 + node; + } + return num; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214.java" new file mode 100644 index 0000000..b04a5ff --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; +import java.util.List; + +/** + * 623. 在二叉树中增加一行 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 在二叉树中增加一行 { + + public static void main(String[] args) { + TreeNode root = TreeNode.buildTree(4, 2, 6, 3, 1, 5); + TreeNode newRoot = new Solution().addOneRow(root, 1, 2); + List list = TreeNode.toValueList(newRoot); + Assertions.assertArrayEquals(new Integer[] { 4, 1, 1, 2, null, null, 6, 3, 1, 5 }, list.toArray()); + + TreeNode root2 = TreeNode.buildTree(4, 2, 6, 3, 1, 5); + TreeNode newRoot2 = new Solution().addOneRow(root2, 1, 1); + List list2 = TreeNode.toValueList(newRoot2); + Assertions.assertArrayEquals(new Integer[] { 1, 4, null, 2, 6, 3, 1, 5 }, list2.toArray()); + } + + static class Solution { + + public TreeNode addOneRow(TreeNode root, int val, int depth) { + if (root == null) { return root; } + if (depth == 1) { + TreeNode newRoot = new TreeNode(val); + newRoot.left = root; + return newRoot; + } + LinkedList q = new LinkedList<>(); + int level = 1; + q.offer(root); + while (!q.isEmpty()) { + + int size = q.size(); + if (level == depth - 1) { + for (int i = 0; i < size; i++) { + TreeNode node = q.poll(); + TreeNode left = node.left; + node.left = new TreeNode(val); + node.left.left = left; + + TreeNode right = node.right; + node.right = new TreeNode(val); + node.right.right = right; + } + break; + } + + // 层序遍历 + for (int i = 0; i < size; i++) { + TreeNode node = q.poll(); + if (node.left != null) { + q.offer(node.left); + } + if (node.right != null) { + q.offer(node.right); + } + } + + level++; + } + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\267\246\345\217\266\345\255\220\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\267\246\345\217\266\345\255\220\344\271\213\345\222\214.java" new file mode 100644 index 0000000..7d24178 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\345\267\246\345\217\266\345\255\220\344\271\213\345\222\214.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 404. 左叶子之和 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 左叶子之和 { + + public static void main(String[] args) { + Assertions.assertEquals(24, + new Solution().sumOfLeftLeaves(TreeNode.buildTree(3, 9, 20, null, null, 15, 7))); + + Assertions.assertEquals(4, + new Solution().sumOfLeftLeaves(TreeNode.buildTree(1, 2, 3, 4, 5))); + + Assertions.assertEquals(0, + new Solution().sumOfLeftLeaves(TreeNode.buildTree(1))); + } + + static class Solution { + + int sum = 0; + + public int sumOfLeftLeaves(TreeNode root) { + traverse(root); + return sum; + } + + public void traverse(TreeNode root) { + if (root == null) { return; } + if (root.left != null && + root.left.left == null && root.left.right == null) { + sum += root.left.val; + } + traverse(root.left); + traverse(root.right); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\346\261\202\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\346\225\260\345\255\227\344\271\213\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\346\261\202\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\346\225\260\345\255\227\344\271\213\345\222\214.java" new file mode 100644 index 0000000..737d9b7 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/traverse/\346\261\202\346\240\271\350\212\202\347\202\271\345\210\260\345\217\266\350\212\202\347\202\271\346\225\260\345\255\227\344\271\213\345\222\214.java" @@ -0,0 +1,60 @@ +package io.github.dunwu.algorithm.tree.btree.traverse; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 求根节点到叶节点数字之和 + * + * @author Zhang Peng + * @date 2025-08-15 + */ +public class 求根节点到叶节点数字之和 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(25, s.sumNumbers(TreeNode.buildTree(1, 2, 3))); + Assertions.assertEquals(1026, s.sumNumbers(TreeNode.buildTree(4, 9, 0, 5, 1))); + } + + static class Solution { + + int res = 0; + LinkedList paths = null; + + public int sumNumbers(TreeNode root) { + res = 0; + paths = new LinkedList<>(); + traverse(root); + return res; + } + + public void traverse(TreeNode root) { + // 【校验】 + if (root == null) { return; } + + // 选择 + paths.addLast(root.val); + if (root.left == null && root.right == null) { + int num = 0; + for (Integer path : paths) { + num = num * 10 + path; + } + res += num; + } + + // 【前序】 + traverse(root.left); + // 【中序】 + traverse(root.right); + // 【后序】 + + // 取消选择 + paths.removeLast(); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\345\261\225\345\274\200\344\270\272\351\223\276\350\241\250.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\345\261\225\345\274\200\344\270\272\351\223\276\350\241\250.java" new file mode 100644 index 0000000..7eb9b65 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\345\261\225\345\274\200\344\270\272\351\223\276\350\241\250.java" @@ -0,0 +1,61 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 114. 二叉树展开为链表 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 二叉树展开为链表 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode root = TreeNode.buildTree(1, 2, 5, 3, 4, null, 6); + s.flatten(root); + Assertions.assertArrayEquals(new Integer[] { 1, null, 2, null, 3, null, 4, null, 5, null, 6 }, + TreeNode.toValueList(root).toArray()); + + TreeNode root2 = TreeNode.buildTree(0); + s.flatten(root2); + Assertions.assertArrayEquals(new Integer[] { 0 }, TreeNode.toValueList(root2).toArray()); + + TreeNode root3 = TreeNode.buildTree(); + s.flatten(root3); + Assertions.assertArrayEquals(new Integer[] {}, TreeNode.toValueList(root3).toArray()); + } + + static class Solution { + + public void flatten(TreeNode root) { + // base case + if (root == null) { return; } + + // 利用定义,把左右子树拉平 + flatten(root.left); + flatten(root.right); + + // *** 后序遍历位置 *** + // 1、左右子树已经被拉平成一条链表 + TreeNode left = root.left; + TreeNode right = root.right; + + // 2、将左子树作为右子树 + root.left = null; + root.right = left; + + // 3、将原先的右子树接到当前右子树的末端 + TreeNode p = root; + while (p.right != null) { + p = p.right; + } + p.right = right; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.java" new file mode 100644 index 0000000..4eac5e3 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.java" @@ -0,0 +1,88 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.LinkedList; + +/** + * 297. 二叉树的序列化与反序列化 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 二叉树的序列化与反序列化 { + + public static void main(String[] args) { + + String input1 = "1,2,3,null,null,4,5,null,null,null,null,"; + String input2 = "null,"; + String input3 = "1,null,null,"; + String input4 = "1,2,null,null,null,"; + + Solution s = new Solution(); + Assertions.assertEquals(input1, s.serialize(s.deserialize(input1))); + Assertions.assertEquals(input2, s.serialize(s.deserialize(input2))); + Assertions.assertEquals(input3, s.serialize(s.deserialize(input3))); + Assertions.assertEquals(input4, s.serialize(s.deserialize(input4))); + } + + static class Solution { + + public static final String SEP = ","; + public static final String NULL = "null"; + + // 主函数,将二叉树序列化为字符串 + public String serialize(TreeNode root) { + StringBuilder sb = new StringBuilder(); + serialize(root, sb); + return sb.toString(); + } + + // 辅助函数,将二叉树存入 StringBuilder + void serialize(TreeNode root, StringBuilder sb) { + if (root == null) { + sb.append(NULL).append(SEP); + return; + } + + // ****** 前序位置 ******** + sb.append(root.val).append(SEP); + + // *********************** + + serialize(root.left, sb); + serialize(root.right, sb); + } + + // 主函数,将字符串反序列化为二叉树结构 + public TreeNode deserialize(String data) { + // 将字符串转化成列表 + LinkedList nodes = new LinkedList<>(); + for (String s : data.split(SEP)) { + nodes.addLast(s); + } + return deserialize(nodes); + } + + // 辅助函数,通过 nodes 列表构造二叉树 + TreeNode deserialize(LinkedList nodes) { + if (nodes.isEmpty()) return null; + + // ****** 前序位置 ******** + // 列表最左侧就是根节点 + String first = nodes.removeFirst(); + if (first.equals(NULL)) return null; + TreeNode root = new TreeNode(Integer.parseInt(first)); + + // ********************* + + root.left = deserialize(nodes); + root.right = deserialize(nodes); + + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" new file mode 100644 index 0000000..c01f06a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" @@ -0,0 +1,82 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 104. 二叉树的最大深度 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 二叉树的最大深度 { + + public static void main(String[] args) { + + Solution s = new Solution(); + Assertions.assertEquals(3, s.maxDepth(TreeNode.buildTree(3, 9, 20, null, null, 15, 7))); + Assertions.assertEquals(2, s.maxDepth(TreeNode.buildTree(1, null, 2))); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(3, s2.maxDepth(TreeNode.buildTree(3, 9, 20, null, null, 15, 7))); + Assertions.assertEquals(2, s2.maxDepth(TreeNode.buildTree(1, null, 2))); + } + + /** + * 【分解】思路解法 + */ + static class Solution { + + public int maxDepth(TreeNode root) { + if (root == null) { return 0; } + + // 计算左右子树的最大深度 + int left = maxDepth(root.left); + int right = maxDepth(root.right); + + // 根据左右子树的最大深度推出原二叉树的最大深度 + // 整棵树的最大深度等于左右子树的最大深度取最大值, + // 然后再加上根节点自己 + return Math.max(left, right) + 1; + } + + } + + /** + * 【遍历】思路解法 + */ + static class Solution2 { + + // 记录最大深度 + int res = 0; + // 记录遍历到的节点的深度 + int depth = 0; + + public int maxDepth(TreeNode root) { + // 重置全局变量 + res = 0; + depth = 0; + traverse(root); + return res; + } + + // 遍历二叉树 + void traverse(TreeNode root) { + if (root == null) { return; } + + // 【前序遍历位置】(进入节点)增加深度 + depth++; + // 遍历到叶子节点时记录最大深度 + if (root.left == null && root.right == null) { + res = Math.max(res, depth); + } + traverse(root.left); + // 【中序遍历位置】 + traverse(root.right); + // 【后序遍历位置】(离开节点)减少深度 + depth--; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.java" new file mode 100644 index 0000000..38bfbb8 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\345\260\217\346\267\261\345\272\246.java" @@ -0,0 +1,36 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 二叉树的最小深度 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 二叉树的最小深度 { + + public static void main(String[] args) { + Solution s = new Solution(); + + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + Assertions.assertEquals(2, s.minDepth(root)); + + TreeNode root2 = TreeNode.buildTree(2, null, 3, null, 4, null, 5, null, 6); + Assertions.assertEquals(5, s.minDepth(root2)); + } + + static class Solution { + + public int minDepth(TreeNode root) { + if (root == null) { return 0; } + int left = minDepth(root.left); + int right = minDepth(root.right); + if (root.left == null || root.right == null) { return left + right + 1; } + return Math.min(left, right) + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" new file mode 100644 index 0000000..976393b --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.java" @@ -0,0 +1,82 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 236. 二叉树的最近公共祖先 + * 解题思路 + * + * @author Zhang Peng + * @date 2020-01-28 + */ +public class 二叉树的最近公共祖先 { + + public static void main(String[] args) { + + Solution s = new Solution(); + TreeNode input = TreeNode.buildTree(6, 2, 8, 0, 4, 7, 9, null, null, 3, 5); + TreeNode output1 = s.lowestCommonAncestor(input, TreeNode.find(input, 2), TreeNode.find(input, 8)); + Assertions.assertNotNull(output1); + Assertions.assertEquals(6, output1.val); + TreeNode output2 = s.lowestCommonAncestor(input, TreeNode.find(input, 2), TreeNode.find(input, 4)); + Assertions.assertNotNull(output2); + Assertions.assertEquals(2, output2.val); + + Solution2 s2 = new Solution2(); + TreeNode input2 = TreeNode.buildTree(6, 2, 8, 0, 4, 7, 9, null, null, 3, 5); + TreeNode output3 = s2.lowestCommonAncestor(input2, TreeNode.find(input2, 2), TreeNode.find(input2, 8)); + Assertions.assertNotNull(output3); + Assertions.assertEquals(6, output3.val); + TreeNode output4 = s2.lowestCommonAncestor(input2, TreeNode.find(input2, 2), TreeNode.find(input2, 4)); + Assertions.assertNotNull(output4); + Assertions.assertEquals(2, output4.val); + } + + static class Solution { + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + + if (root == null) return null; + if (root == p || root == q) return root; + + TreeNode left = lowestCommonAncestor(root.left, p, q); + TreeNode right = lowestCommonAncestor(root.right, p, q); + + if (left != null && right != null) { return root; } + return left != null ? left : right; + } + + } + + static class Solution2 { + + private TreeNode res = null; + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + res = null; + return find(root, p.val, q.val); + } + + TreeNode find(TreeNode root, int p, int q) { + if (root == null) { return null; } + // 如果已经找到 LCA 节点,直接返回 + if (res != null) { return res; } + + if (root.val == p || root.val == q) return root; + + TreeNode left = find(root.left, p, q); + TreeNode right = find(root.right, p, q); + + if (left != null && right != null) { + // 当前节点是 LCA 节点,记录下来 + res = root; + return res; + } + return left != null ? left : right; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\347\233\264\345\276\204.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\347\233\264\345\276\204.java" new file mode 100644 index 0000000..c55a9d6 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\347\233\264\345\276\204.java" @@ -0,0 +1,43 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 543. 二叉树的直径 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 二叉树的直径 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(3, s.diameterOfBinaryTree(TreeNode.buildTree(1, 2, 3, 4, 5))); + Assertions.assertEquals(1, s.diameterOfBinaryTree(TreeNode.buildTree(1, 2))); + Assertions.assertEquals(0, s.diameterOfBinaryTree(TreeNode.buildTree(1))); + Assertions.assertEquals(2, s.diameterOfBinaryTree(TreeNode.buildTree(2, 3, null, 1))); + } + + static class Solution { + + private int max = 0; + + public int diameterOfBinaryTree(TreeNode root) { + max = 0; + depth(root); + return max; + } + + public int depth(TreeNode root) { + if (root == null) { return 0; } + int left = depth(root.left); + int right = depth(root.right); + int depth = Math.max(left, right) + 1; + max = Math.max(max, left + right); + return depth; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\346\254\241\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\346\254\241\351\201\215\345\216\206.java" new file mode 100644 index 0000000..1b91f8f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\272\214\345\217\211\346\240\221\347\232\204\351\224\257\351\275\277\345\275\242\345\261\202\346\254\241\351\201\215\345\216\206.java" @@ -0,0 +1,73 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * 103. 二叉树的锯齿形层次遍历 + * + * @author Zhang Peng + * @date 2025-08-18 + */ +public class 二叉树的锯齿形层次遍历 { + + public static void main(String[] args) { + + Solution s = new Solution(); + + TreeNode root = TreeNode.buildTree(3, 9, 20, null, null, 15, 7); + List> expect = new LinkedList<>(); + expect.add(Arrays.asList(3)); + expect.add(Arrays.asList(20, 9)); + expect.add(Arrays.asList(15, 7)); + Assertions.assertArrayEquals(expect.toArray(), s.zigzagLevelOrder(root).toArray()); + + TreeNode root2 = TreeNode.buildTree(1); + List> expect2 = new LinkedList<>(); + expect2.add(Arrays.asList(1)); + Assertions.assertArrayEquals(expect2.toArray(), s.zigzagLevelOrder(root2).toArray()); + + TreeNode root3 = TreeNode.buildTree(); + List> expect3 = new LinkedList<>(); + Assertions.assertArrayEquals(expect3.toArray(), s.zigzagLevelOrder(root3).toArray()); + } + + static class Solution { + + public List> zigzagLevelOrder(TreeNode root) { + + if (root == null) { return new LinkedList<>(); } + + LinkedList q = new LinkedList<>(); + LinkedList> res = new LinkedList<>(); + q.offer(root); + + boolean reverse = false; + while (!q.isEmpty()) { + int size = q.size(); + List cur = new LinkedList<>(); + + for (int i = 0; i < size; i++) { + TreeNode node = q.poll(); + cur.add(node.val); + if (node.left != null) { q.offer(node.left); } + if (node.right != null) { q.offer(node.right); } + } + if (reverse) { + Collections.reverse(cur); + } + res.add(cur); + reverse = !reverse; + } + + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..729b3c0 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\344\273\216\345\205\210\345\272\217\351\201\215\345\216\206\350\277\230\345\216\237\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,76 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; + +import java.util.Stack; + +/** + * @author Zhang Peng + * @since 2020-06-18 + */ +public class 从先序遍历还原二叉树 { + + public static void main(String[] args) { + TreeNode result = recoverFromPreorder("1-2--3--4-5--6--7"); + System.out.println(TreeNode.toList(result)); + } + + public static TreeNode recoverFromPreorder(String S) { + if (S == null || S.length() == 0) return null; + + Stack stack = new Stack<>(); + for (int i = 0; i < S.length(); i++) { + int level = 0; + + // 获取节点深度 + if (S.charAt(i) == '-') { + while (i < S.length() && S.charAt(i) == '-') { + level++; + i++; + } + } + + // 获取节点值 + int data = 0; + if (S.charAt(i) != '-') { + while (i < S.length() && S.charAt(i) != '-') { + data = data * 10 + (S.charAt(i) - '0'); + i++; + } + i--; + } + + // 打印必然成对出现的 节点深度 和 节点值 + System.out.printf("level = %d, num = %d\n", level, data); + + // 构建新节点 + TreeNode node = new TreeNode(data); + + // 栈为空,push 栈顶节点 + if (stack.isEmpty()) { + stack.push(node); + continue; + } + + // 如果栈的 size > 当前节点的 level,即 栈顶节点的 level >= 当前节点的level + // 则说明栈顶不是父亲,栈顶遇到的已经是别人的儿子 + // 说明栈顶的儿子已经找齐了,即自己的子树已经构建完了,该出栈了(子调用有了结果) + while (stack.size() > level) { + stack.pop(); + } + + TreeNode parent = stack.peek(); + if (parent.left == null) { + parent.left = node; + } else { + parent.right = node; + } + stack.push(node); + } + while (stack.size() > 1) { + stack.pop(); + } + return stack.pop(); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" new file mode 100644 index 0000000..03dc2b9 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221.java" @@ -0,0 +1,54 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * 叶子相似的树 算法实现 + * + *

+ * 请考虑一颗二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。
+ *
+ *
+ *
+ * 举个例子,如上图所示,给定一颗叶值序列为 (6, 7, 4, 9, 8) 的树。
+ *
+ * 如果有两颗二叉树的叶值序列是相同,那么我们就认为它们是 叶相似 的。
+ *
+ * 如果给定的两个头结点分别为 root1 和 root2 的树是叶相似的,则返回 true;否则返回 false 。
+ *
+ * 提示:
+ *
+ * 给定的两颗树可能会有 1 到 100 个结点。
+ * 
+ * + * @see 叶子相似的树 + */ +public class 叶子相似的树 { + + public static void main(String[] args) { + TreeNode tree1 = TreeNode.buildTree(3, 5, 1, 6, 2, 9, 8, null, null, 7, 4); + TreeNode tree2 = TreeNode.buildTree(3, 5, 1, 6, 7, 4, 2, null, null, null, null, null, null, 9, 8); + Assertions.assertTrue(leafSimilar(tree1, tree2)); + } + + public static boolean leafSimilar(TreeNode root1, TreeNode root2) { + List leafs1 = new LinkedList<>(); + List leafs2 = new LinkedList<>(); + leafNodes(root1, leafs1); + leafNodes(root2, leafs2); + return Arrays.equals(leafs1.toArray(), leafs2.toArray()); + } + + public static void leafNodes(TreeNode root, List leafs) { + if (root == null) { return; } + if (root.left == null && root.right == null) { leafs.add(root.val); } + leafNodes(root.left, leafs); + leafNodes(root.right, leafs); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\350\212\202\347\202\271\344\270\252\346\225\260.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\350\212\202\347\202\271\344\270\252\346\225\260.java" new file mode 100644 index 0000000..e6aeaec --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\350\212\202\347\202\271\344\270\252\346\225\260.java" @@ -0,0 +1,30 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 222. 完全二叉树的节点个数 + * + * @author Zhang Peng + * @since 2020-07-06 + */ +public class 完全二叉树的节点个数 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(6, s.countNodes(TreeNode.buildTree(1, 2, 3, 4, 5, 6))); + Assertions.assertEquals(0, s.countNodes(TreeNode.buildTree())); + Assertions.assertEquals(1, s.countNodes(TreeNode.buildTree(1))); + } + + static class Solution { + + public int countNodes(TreeNode root) { + if (root == null) { return 0; } + return countNodes(root.left) + countNodes(root.right) + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..9ac1276 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\346\234\200\345\244\247\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,46 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 654. 最大二叉树 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 最大二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode output = s.constructMaximumBinaryTree(new int[] { 3, 2, 1, 6, 0, 5 }); + Assertions.assertEquals(TreeNode.buildTree(6, 3, 5, null, 2, 0, null, null, 1), output); + TreeNode output2 = s.constructMaximumBinaryTree(new int[] { 3, 2, 1 }); + Assertions.assertEquals(TreeNode.buildTree(3, null, 2, null, 1), output2); + } + + static class Solution { + + public TreeNode constructMaximumBinaryTree(int[] nums) { + return dfs(nums, 0, nums.length - 1); + } + + public TreeNode dfs(int[] nums, int start, int end) { + if (start > end) { return null; } + int max = -1; + int maxIndex = start; + for (int i = start; i <= end; i++) { + if (nums[i] > max) { + maxIndex = i; + max = nums[i]; + } + } + TreeNode root = new TreeNode(nums[maxIndex]); + root.left = dfs(nums, start, maxIndex - 1); + root.right = dfs(nums, maxIndex + 1, end); + return root; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.java" new file mode 100644 index 0000000..30885c1 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\347\277\273\350\275\254\344\272\214\345\217\211\346\240\221.java" @@ -0,0 +1,79 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 226. 翻转二叉树 + * + * @author Zhang Peng + * @date 2025-08-11 + */ +public class 翻转二叉树 { + + public static void main(String[] args) { + Solution s = new Solution(); + Assertions.assertEquals(TreeNode.buildTree(4, 2, 7, 1, 3, 6, 9), + s.invertTree(TreeNode.buildTree(4, 7, 2, 9, 6, 3, 1))); + Assertions.assertEquals(TreeNode.buildTree(2, 3, 1), + s.invertTree(TreeNode.buildTree(2, 1, 3))); + Assertions.assertEquals(TreeNode.buildTree(), + s.invertTree(TreeNode.buildTree())); + + Solution2 s2 = new Solution2(); + Assertions.assertEquals(TreeNode.buildTree(4, 2, 7, 1, 3, 6, 9), + s2.invertTree(TreeNode.buildTree(4, 7, 2, 9, 6, 3, 1))); + Assertions.assertEquals(TreeNode.buildTree(2, 3, 1), + s2.invertTree(TreeNode.buildTree(2, 1, 3))); + Assertions.assertEquals(TreeNode.buildTree(), + s2.invertTree(TreeNode.buildTree())); + } + + /** + * 【分解】思路解法 + */ + static class Solution { + + public TreeNode invertTree(TreeNode root) { + if (root == null) { return root; } + TreeNode left = invertTree(root.left); + TreeNode right = invertTree(root.right); + root.right = left; + root.left = right; + return root; + } + + } + + /** + * 【遍历】思路解法 + */ + static class Solution2 { + + public TreeNode invertTree(TreeNode root) { + traverse(root); + return root; + } + + // 遍历二叉树 + void traverse(TreeNode root) { + if (root == null) { return; } + + // 【前序】 + // System.out.printf("[node -> left]从节点 %s 进入节点 %s\n", root, root.left); + traverse(root.left); + // 【中序】 + // System.out.printf("\t[left -> node]从节点 %s 回到节点 %s\n", root.left, root); + // System.out.printf("\t[node -> right]从节点 %s 进入节点 %s\n", root, root.right); + traverse(root.right); + // 【后序】 + // System.out.printf("\t[right -> node]从节点 %s 回到节点 %s\n", root.right, root); + + TreeNode temp = root.left; + root.left = root.right; + root.right = temp; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\350\267\257\345\276\204\346\200\273\345\222\214.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\350\267\257\345\276\204\346\200\273\345\222\214.java" new file mode 100644 index 0000000..28ae963 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/\350\267\257\345\276\204\346\200\273\345\222\214.java" @@ -0,0 +1,33 @@ +package io.github.dunwu.algorithm.tree.btree; + +import io.github.dunwu.algorithm.tree.TreeNode; +import org.junit.jupiter.api.Assertions; + +/** + * 112. 路径总和 + * + * @author Zhang Peng + * @date 2020-01-29 + */ +public class 路径总和 { + + public static void main(String[] args) { + Solution s = new Solution(); + TreeNode tree = TreeNode.buildTree(5, 4, 8, 11, null, 13, 4, 7, 2, null, null, null, null, null, 1); + Assertions.assertTrue(s.hasPathSum(tree, 22)); + TreeNode tree2 = TreeNode.buildTree(1, 2); + Assertions.assertFalse(s.hasPathSum(tree2, 1)); + } + + static class Solution { + + public boolean hasPathSum(TreeNode root, int targetSum) { + if (root == null) { return false; } + if (root.left == null && root.right == null && root.val == targetSum) { return true; } + return hasPathSum(root.left, targetSum - root.val) + || hasPathSum(root.right, targetSum - root.val); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..3f2138f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\211\215\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,47 @@ +package io.github.dunwu.algorithm.tree.ntree; + +import io.github.dunwu.algorithm.tree.Node; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 589. N 叉树的前序遍历 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class N叉树的前序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Node node3 = new Node(3); + node3.children.add(new Node(5)); + node3.children.add(new Node(6)); + Node root = new Node(1); + root.children.add(node3); + root.children.add(new Node(2)); + root.children.add(new Node(4)); + Assertions.assertArrayEquals(new Integer[] { 1, 3, 5, 6, 2, 4 }, s.preorder(root).toArray()); + } + + static class Solution { + + public List preorder(Node root) { + List res = new ArrayList<>(); + dfs(root, res); + return res; + } + + public void dfs(Node root, List res) { + if (root == null) { return; } + res.add(root.val); + for (int i = 0; i < root.children.size(); i++) { + dfs(root.children.get(i), res); + } + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..17db4ac --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,47 @@ +package io.github.dunwu.algorithm.tree.ntree; + +import io.github.dunwu.algorithm.tree.Node; +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.List; + +/** + * 589. N 叉树的前序遍历 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class N叉树的后序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Node node3 = new Node(3); + node3.children.add(new Node(5)); + node3.children.add(new Node(6)); + Node root = new Node(1); + root.children.add(node3); + root.children.add(new Node(2)); + root.children.add(new Node(4)); + Assertions.assertArrayEquals(new Integer[] { 5, 6, 3, 2, 4, 1 }, s.postorder(root).toArray()); + } + + static class Solution { + + public List postorder(Node root) { + List res = new ArrayList<>(); + dfs(root, res); + return res; + } + + public void dfs(Node root, List res) { + if (root == null) { return; } + for (int i = 0; i < root.children.size(); i++) { + dfs(root.children.get(i), res); + } + res.add(root.val); + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.java" new file mode 100644 index 0000000..ca4152f --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\345\261\202\345\272\217\351\201\215\345\216\206.java" @@ -0,0 +1,61 @@ +package io.github.dunwu.algorithm.tree.ntree; + +import io.github.dunwu.algorithm.tree.Node; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * 429. N 叉树的层序遍历 + * + * @author Zhang Peng + * @date 2025-10-27 + */ +public class N叉树的层序遍历 { + + public static void main(String[] args) { + Solution s = new Solution(); + Node node3 = new Node(3); + node3.children.add(new Node(5)); + node3.children.add(new Node(6)); + Node root = new Node(1); + root.children.add(node3); + root.children.add(new Node(2)); + root.children.add(new Node(4)); + List> res = s.levelOrder(root); + System.out.printf("res: %s\n", res); + } + + static class Solution { + + public List> levelOrder(Node root) { + List> res = new ArrayList<>(); + LinkedList queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + int size = queue.size(); + LinkedList list = new LinkedList<>(); + for (int i = 0; i < size; i++) { + Node node = queue.poll(); + if (node == null) { + continue; + } + list.add(node.val); + if (!node.children.isEmpty()) { + for (Node child : node.children) { + queue.offer(child); + } + } + } + if (!list.isEmpty()) { + res.add(list); + } + } + return res; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" new file mode 100644 index 0000000..b4ab98a --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/ntree/N\345\217\211\346\240\221\347\232\204\346\234\200\345\244\247\346\267\261\345\272\246.java" @@ -0,0 +1,49 @@ +package io.github.dunwu.algorithm.tree.ntree; + +import io.github.dunwu.algorithm.tree.Node; +import org.junit.jupiter.api.Assertions; + +/** + * 559. N叉树的最大深度 + * + * @author Zhang Peng + * @date 2020-03-23 + */ +public class N叉树的最大深度 { + + public static void main(String[] args) { + Solution s = new Solution(); + io.github.dunwu.algorithm.tree.Node node3 = new io.github.dunwu.algorithm.tree.Node(3); + node3.children.add(new io.github.dunwu.algorithm.tree.Node(5)); + node3.children.add(new io.github.dunwu.algorithm.tree.Node(6)); + io.github.dunwu.algorithm.tree.Node root = new io.github.dunwu.algorithm.tree.Node(1); + root.children.add(node3); + root.children.add(new io.github.dunwu.algorithm.tree.Node(2)); + root.children.add(new io.github.dunwu.algorithm.tree.Node(4)); + Assertions.assertEquals(3, s.maxDepth(root)); + } + + static class Solution { + + private int max = 0; + + public int maxDepth(Node root) { + max = 0; + dfs(root); + return max; + } + + public int dfs(Node root) { + if (root == null) { return 0; } + + int depth = 0; + for (int i = 0; i < root.children.size(); i++) { + depth = Math.max(depth, dfs(root.children.get(i))); + } + max = Math.max(max, depth + 1); + return depth + 1; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\344\272\214\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\344\272\214\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" new file mode 100644 index 0000000..7fbb430 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\344\272\214\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" @@ -0,0 +1,30 @@ +package io.github.dunwu.algorithm.tree.template; + +import io.github.dunwu.algorithm.tree.TreeNode; + +/** + * 二叉树递归遍历框架 + * + * @author Zhang Peng + * @date 2025-10-23 + */ +public class 二叉树遍历框架 { + + /** + * 二叉树的遍历框架 + */ + public void traverse(TreeNode root) { + // 【校验】 + if (root == null) { return; } + // 【前序】 + System.out.printf("[node -> left]从节点 %s 进入节点 %s\n", root, root.left); + traverse(root.left); + // 【中序】 + System.out.printf("\t[left -> node]从节点 %s 回到节点 %s\n", root.left, root); + System.out.printf("\t[node -> right]从节点 %s 进入节点 %s\n", root, root.right); + traverse(root.right); + // 【后序】 + System.out.printf("\t[right -> node]从节点 %s 回到节点 %s\n", root.right, root); + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\345\244\232\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\345\244\232\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" new file mode 100644 index 0000000..a8154e0 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/template/\345\244\232\345\217\211\346\240\221\351\201\215\345\216\206\346\241\206\346\236\266.java" @@ -0,0 +1,97 @@ +package io.github.dunwu.algorithm.tree.template; + +import io.github.dunwu.algorithm.tree.Node; +import io.github.dunwu.algorithm.tree.State; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 多叉树遍历框架 + * + * @author Zhang Peng + * @date 2025-11-03 + */ +public class 多叉树遍历框架 { + + // 多叉树的遍历框架 + void dfs(Node root) { + // base case + if (root == null) { + return; + } + // 前序位置 + System.out.println("visit " + root.val); + for (Node child : root.children) { + dfs(child); + } + // 后序位置 + } + + void bfs(Node root) { + // base case + if (root == null) { + return; + } + Queue q = new LinkedList<>(); + q.offer(root); + while (!q.isEmpty()) { + Node node = q.poll(); + // 访问 cur 节点 + System.out.println(node.val); + + // 把 cur 的所有子节点加入队列 + for (Node child : node.children) { + q.offer(child); + } + } + } + + // 记录遍历步数的写法 + void bfs2(Node root) { + if (root == null) { + return; + } + Queue q = new LinkedList<>(); + q.offer(root); + // 记录当前遍历到的层数(根节点视为第 1 层) + int depth = 1; + + while (!q.isEmpty()) { + int size = q.size(); + for (int i = 0; i < size; i++) { + Node node = q.poll(); + // 访问 cur 节点,同时知道它所在的层数 + System.out.println("depth = " + depth + ", val = " + node.val); + + for (Node child : node.children) { + q.offer(child); + } + } + depth++; + } + } + + // 每个节点自行维护 State 类,记录深度等信息 + void bfs3(Node root) { + if (root == null) { + return; + } + Queue q = new LinkedList<>(); + // 记录当前遍历到的层数(根节点视为第 1 层) + q.offer(new State(root, 1)); + + while (!q.isEmpty()) { + State state = q.poll(); + Node node = state.node; + int depth = state.depth; + // 访问 cur 节点,同时知道它所在的层数 + System.out.println("depth = " + depth + ", val = " + node.val); + + for (Node child : node.children) { + q.offer(new State(child, depth + 1)); + } + } + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/Trie.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/Trie.java new file mode 100644 index 0000000..0f616fb --- /dev/null +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/Trie.java @@ -0,0 +1,74 @@ +package io.github.dunwu.algorithm.trie; + +import java.util.concurrent.atomic.AtomicInteger; + +public class Trie { + + private final AtomicInteger wordCount = new AtomicInteger(0); + private final TrieNode root = new TrieNode('/'); // 存储无意义字符 + + // 往 Trie 树中插入一个字符串 + public void insert(char[] text) { + TrieNode p = root; + for (char c : text) { + int index = c - 'a'; + if (p.children[index] == null) { + p.children[index] = new TrieNode(c); + } else { + p.children[index].count++; + } + p = p.children[index]; + } + wordCount.getAndIncrement(); + p.isEnd = true; + } + + // 在 Trie 树中查找一个字符串 + public boolean find(char[] pattern) { + TrieNode p = root; + for (int i = 0; i < pattern.length; ++i) { + int index = pattern[i] - 'a'; + if (p.children[index] == null) { + return false; // 不存在 pattern + } + p = p.children[index]; + } + return p.isEnd; + } + + public String longest() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 26; i++) { + method(root.children[i], sb); + } + return sb.toString(); + } + + public void method(TrieNode root, StringBuilder sb) { + if (root != null) { + if (root.count == wordCount.get()) { + sb.append(root.data); + if (root.children != null) { + for (int i = 0; i < 26; i++) { + method(root.children[i], sb); + } + } + } + } + } + + public class TrieNode { + + public int count; + public char data; + public boolean isEnd = false; + public TrieNode[] children = new TrieNode[26]; + + public TrieNode(char data) { + this.count = 1; + this.data = data; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242.java" new file mode 100644 index 0000000..f6d2890 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242.java" @@ -0,0 +1,460 @@ +package io.github.dunwu.algorithm.trie; + +import org.junit.jupiter.api.Assertions; + +/** + * @author Zhang Peng + * @see 79. 单词搜索 + * @since 2020-07-04 + */ +public class 单词搜索 { + + public static void main(String[] args) { + char[][] board = { + { 'A', 'B', 'C', 'E' }, + { 'S', 'F', 'C', 'S' }, + { 'A', 'D', 'E', 'E' } + }; + 单词搜索 demo = new 单词搜索(); + // Assertions.assertFalse(demo.exist(board, "ABCB")); + Assertions.assertTrue(demo.exist(board, "SEE")); + Assertions.assertTrue(demo.exist(board, "ABCCED")); + + char[][] board2 = + { { 'p', 'f', 'h', 'n', 'u', 'v', 'u', 'z', 'z', 'p', 'u', 'j', 's', 't', 'p', 'i', 'v', 'o', 's', 'm', 'q', + 'l', 'c', 't', 'y', 'n', 's', 'p', 'u', 't', 'y', 'l', 'y', 'i', 'a', 'u', 'f', 'u', 't', 'f', 'j', 'n', + 'q', 'd', 'j', 'f', 'n', 'e', 'v', 'f', 't', 'o', 't', 'v', 'h', 't', 'i', 'c', 'j', 'e', 'v', 'j', 'r', + 'v', 'r', 'r', 'p', 'x', 'l' }, + { 'k', 'e', 'w', 'z', 'u', 'b', 'i', 'b', 'p', 'p', 'w', 'w', 'i', 'k', 'p', 'd', 'u', 'l', 'n', 'w', + 'u', 's', 'r', 'd', 'j', 'o', 'x', 'b', 'd', 'w', 'o', 'p', 'a', 'l', 'o', 'w', 'o', 'x', 'x', 'd', + 'o', 't', 'c', 'w', 'g', 'r', 'z', 'a', 'e', 'o', 'w', 'y', 'g', 'p', 'e', 's', 'd', 'd', 'v', 'i', + 'z', 'j', 'z', 'b', 'w', 'n', 'x', 'l', 'm' }, + { 'u', 'q', 'a', 'n', 's', 'w', 't', 'm', 'x', 'v', 'q', 'o', 'r', 'r', 'u', 'i', 'x', 'o', 'n', 'a', + 'j', 'v', 'b', 'v', 'u', 'c', 't', 'k', 'z', 'g', 'w', 't', 'x', 'z', 'i', 'r', 'x', 'e', 'd', 'x', + 'z', 'w', 'l', 't', 'p', 'h', 'b', 'm', 'w', 'p', 'm', 'h', 'm', 'n', 'c', 'j', 'p', 'w', 'v', 'q', + 'c', 't', 'j', 'b', 's', 't', 't', 's', 'x' }, + { 'y', 'p', 'x', 'w', 'c', 'q', 'l', 'j', 't', 'x', 'f', 'k', 'l', 'p', 'x', 'a', 'r', 'i', 'r', 'p', + 'd', 'h', 'u', 'w', 's', 'x', 'p', 'm', 'q', 'j', 'j', 'r', 'y', 'i', 'n', 'a', 'a', 'b', 'l', 'u', + 'y', 't', 'e', 'k', 'i', 'd', 'k', 'b', 'l', 'e', 'r', 'o', 'l', 'n', 'n', 'e', 'k', 'e', 'q', 'd', + 'n', 'b', 'u', 'l', 'k', 'j', 'n', 'k', 'k' }, + { 'y', 'g', 'l', 't', 'l', 'x', 'b', 'q', 'h', 'd', 'c', 'l', 'w', 's', 'z', 'j', 'f', 'f', 't', 'j', + 'v', 'w', 'w', 'y', 's', 'j', 'k', 'e', 'y', 'x', 'q', 'x', 'd', 'b', 'q', 'q', 'y', 'u', 'h', 'i', + 'x', 'l', 'v', 'v', 'd', 'u', 'e', 'l', 'b', 'z', 'w', 'w', 'y', 't', 'x', 'q', 'e', 'h', 'w', 'd', + 'e', 'n', 'c', 'k', 'q', 'u', 'a', 'r', 'q' }, + { 'j', 'z', 'n', 'u', 'w', 'i', 'a', 't', 'o', 'n', 'u', 'o', 'j', 't', 'm', 'e', 'q', 'e', 'j', 'z', + 'b', 'o', 'g', 'q', 's', 'q', 'i', 'm', 's', 'z', 'd', 'c', 'y', 's', 'y', 'v', 'd', 'a', 'q', 'r', + 'n', 'k', 'h', 'x', 'd', 't', 'd', 't', 'y', 'o', 'v', 'b', 'c', 'b', 't', 'w', 'r', 'b', 'j', 'l', + 'b', 'o', 'n', 'b', 'i', 'o', 'y', 'n', 'q' }, + { 'o', 'h', 'e', 'z', 'o', 'd', 'c', 'i', 'i', 'y', 'i', 'x', 'v', 'l', 'b', 'w', 'e', 'y', 'p', 'f', + 'j', 'a', 'g', 'x', 'q', 'i', 'h', 'e', 'g', 'v', 'u', 'v', 'c', 'a', 'w', 's', 'f', 'a', 'c', 'o', + 'y', 'k', 'l', 'v', 'v', 'm', 'r', 'b', 'k', 'g', 'h', 't', 'h', 'n', 's', 'x', 'x', 'c', 'd', 'e', + 'x', 'z', 'b', 'b', 'c', 'x', 't', 'h', 'x' }, + { 'w', 'x', 'y', 'i', 'i', 't', 'g', 'x', 'n', 'h', 'j', 't', 'q', 'f', 'c', 'g', 'x', 'b', 'd', 'b', + 'e', 'j', 'y', 'g', 'm', 'b', 'i', 'l', 'x', 'r', 'j', 'v', 'p', 'j', 'f', 'z', 'e', 'l', 'w', 't', + 'v', 'i', 'n', 'l', 'n', 'r', 't', 'm', 't', 'x', 'o', 'z', 'i', 'o', 'f', 'v', 'q', 'n', 'g', 'n', + 'f', 'r', 'k', 'w', 'c', 'p', 'v', 'h', 'd' }, + { 's', 'c', 'y', 'c', 'p', 'l', 'p', 'j', 'h', 'b', 'c', 'e', 'p', 'b', 'm', 'e', 'h', 'h', 'w', 'w', + 'q', 'l', 'd', 'h', 'v', 'z', 'k', 'k', 'v', 't', 'n', 'p', 'v', 'n', 'r', 'n', 'b', 'g', 'w', 'i', + 'j', 'y', 'o', 'z', 'b', 'c', 'f', 'k', 'm', 'b', 'h', 'c', 'm', 'k', 'j', 'j', 'm', 'v', 't', 'h', + 'o', 'j', 'y', 'k', 'y', 'r', 'z', 'b', 'x' }, + { 'v', 'j', 'i', 'v', 'z', 'j', 'y', 'c', 'o', 'j', 'o', 'r', 'q', 'q', 'f', 'c', 'b', 'o', 'o', 'x', + 'i', 'x', 'l', 't', 'v', 'x', 'r', 'o', 'w', 't', 'n', 't', 'e', 'w', 'q', 'e', 'f', 'p', 'g', 'w', + 'y', 'w', 'n', 'q', 'o', 't', 'u', 'p', 'h', 'j', 'm', 'r', 'i', 'a', 'k', 'g', 'z', 'c', 'u', 'y', + 'x', 'i', 't', 'b', 'e', 'k', 'f', 'l', 'z' }, + { 'n', 'h', 'z', 'j', 'v', 'p', 'z', 'q', 'l', 'p', 'x', 'w', 'd', 'p', 'f', 'f', 'b', 'n', 'f', 'd', + 'h', 'f', 'a', 'r', 'y', 'c', 'x', 'i', 'j', 'j', 'j', 'z', 'q', 'i', 'k', 'n', 'z', 'k', 'd', 'l', + 'z', 'd', 'h', 'e', 's', 'o', 'k', 't', 'b', 'p', 'z', 'l', 'u', 'b', 'c', 'u', 'f', 'a', 'd', 'r', + 'l', 'o', 'q', 'b', 'z', 'a', 'r', 'a', 'k' }, + { 'u', 'n', 'l', 'x', 'x', 'q', 'r', 'l', 'a', 'n', 'p', 'r', 'o', 'a', 'n', 'p', 'e', 'h', 'v', 'e', + 'm', 'm', 'p', 'b', 'e', 't', 'c', 'e', 'k', 'c', 'r', 'g', 'q', 'e', 'g', 'n', 'w', 'x', 'a', 'y', + 'm', 'p', 'p', 'a', 'r', 'c', 'q', 'w', 'm', 'n', 'a', 'y', 'b', 's', 'b', 'f', 'n', 'd', 'l', 'x', + 'g', 'c', 'f', 'w', 'j', 'l', 'l', 'f', 'j' }, + { 'l', 'e', 'v', 'd', 'v', 'w', 'u', 'y', 'o', 'q', 'm', 'b', 't', 'k', 'c', 'l', 'm', 'j', 'a', 'r', + 'u', 'x', 'x', 'x', 'e', 'v', 'i', 'q', 'i', 'n', 'b', 'u', 't', 'w', 'x', 'p', 'u', 'r', 'p', 'k', + 'k', 'd', 'n', 'd', 'n', 'r', 'q', 'b', 'a', 'q', 't', 'v', 'p', 's', 'u', 't', 'q', 'c', 'j', 'y', + 'r', 'k', 's', 'n', 'j', 'r', 'e', 'f', 'l' }, + { 'v', 's', 'v', 'y', 'f', 'a', 'n', 'x', 's', 'p', 'x', 'i', 'k', 'u', 'z', 'e', 'o', 'u', 'u', 's', + 'g', 't', 'm', 'q', 'n', 'z', 'b', 'f', 'f', 'j', 'q', 'a', 'b', 'n', 'y', 'g', 'p', 'n', 'f', 'h', + 'c', 'f', 'r', 'o', 'z', 'q', 't', 'o', 'k', 'n', 'i', 'q', 'i', 'u', 'j', 'w', 'v', 'm', 'b', 'a', + 'v', 't', 'a', 'y', 'i', 'a', 'h', 'x', 'o' }, + { 'm', 'e', 's', 'r', 'v', 'j', 'r', 'n', 'c', 'h', 'x', 'r', 'r', 'q', 'a', 'm', 'b', 'w', 'j', 'n', + 'z', 'k', 'l', 's', 'm', 'j', 'c', 'p', 's', 'z', 'f', 'f', 'f', 'x', 'y', 'c', 'g', 'r', 'r', 'k', + 'a', 'q', 'e', 's', 'g', 'e', 'g', 'h', 'c', 'p', 'v', 'b', 'b', 'i', 'v', 'q', 'r', 'x', 'f', 'm', + 'y', 'k', 'r', 'f', 'j', 'r', 'h', 's', 'j' }, + { 'a', 'e', 'j', 'q', 'i', 'd', 'x', 'm', 'j', 'g', 'q', 'b', 'b', 'r', 'e', 'l', 'o', 'u', 'f', 'n', + 'z', 'r', 'l', 'l', 'i', 's', 'x', 'b', 'b', 'p', 'k', 'b', 't', 'w', 's', 'c', 'b', 'p', 'q', 'n', + 'v', 'h', 'o', 'z', 'a', 's', 'k', 'p', 'n', 'p', 'c', 'o', 'i', 'q', 'c', 'q', 'i', 'z', 's', 'k', + 'o', 'e', 'l', 'j', 'c', 'f', 'l', 'e', 'u' }, + { 'c', 'r', 's', 'l', 'h', 't', 'l', 'b', 'f', 'a', 'q', 'x', 'f', 'f', 'f', 'v', 'h', 'y', 'f', 'g', + 's', 'r', 'w', 'w', 'f', 'f', 'b', 'k', 't', 'f', 'h', 'x', 'y', 'b', 'i', 'f', 'u', 't', 'g', 'b', + 'w', 'x', 'y', 'b', 'e', 'e', 'y', 'l', 'e', 'f', 't', 'w', 'x', 'r', 'u', 'c', 'y', 'v', 'o', 'r', + 'c', 'v', 'o', 'a', 'y', 'y', 'f', 's', 's' }, + { 'o', 'u', 'q', 'l', 'u', 't', 'p', 'a', 'r', 'c', 'e', 'w', 'x', 'c', 'v', 'q', 'x', 'x', 'o', 'u', + 'o', 'g', 'x', 'j', 'w', 'x', 'i', 'v', 'f', 'c', 'p', 't', 'y', 'f', 'g', 'v', 'y', 'x', 'v', 'r', + 'b', 'c', 'p', 'y', 'e', 'l', 'o', 'b', 'k', 'e', 'y', 'y', 'k', 'v', 'k', 'j', 'u', 'u', 'e', 'z', + 'y', 't', 'u', 'x', 'a', 'a', 'u', 'a', 'x' }, + { 'p', 't', 'y', 't', 'i', 'y', 'y', 't', 'o', 'b', 'e', 't', 'z', 'e', 'f', 'u', 'o', 'o', 'p', 'k', + 's', 'q', 'j', 'n', 'l', 'i', 'p', 'n', 'c', 'p', 'n', 't', 'i', 'l', 'n', 't', 'm', 'l', 'o', 'c', + 'm', 'u', 'v', 'o', 'z', 'd', 'i', 'p', 'r', 'z', 'a', 'm', 's', 'j', 'b', 'f', 'r', 'r', 's', 't', + 'i', 'f', 'm', 't', 't', 'b', 'm', 'f', 'm' }, + { 'a', 'h', 'b', 'x', 'f', 'p', 'w', 'i', 'z', 'l', 'b', 'b', 'l', 'p', 't', 'w', 'r', 'y', 'n', 'k', + 'q', 'i', 's', 'y', 'x', 'n', 'r', 'y', 'b', 'y', 'n', 'e', 'h', 'o', 'b', 'o', 'd', 'x', 'y', 'e', + 'k', 'a', 'f', 'y', 'p', 'y', 'u', 'i', 'y', 'k', 's', 'p', 's', 'n', 'p', 'r', 'a', 'g', 'q', 'e', + 'g', 'd', 'i', 'n', 't', 'l', 'e', 'y', 'i' }, + { 'c', 'c', 'u', 'c', 'i', 's', 'u', 'i', 'n', 'c', 'h', 'x', 'v', 'y', 'r', 'i', 'n', 'j', 'k', 't', + 'z', 'o', 'b', 'e', 'y', 'q', 'x', 'j', 'u', 'v', 't', 'x', 'x', 'o', 'b', 'h', 'g', 'v', 'q', 'v', + 'y', 'x', 'u', 'v', 'x', 'm', 'f', 'k', 'v', 'p', 'f', 'w', 'g', 'g', 'a', 'e', 'w', 'z', 'o', 't', + 'u', 'h', 'q', 't', 'x', 'r', 'b', 'e', 'n' }, + { 't', 'b', 'n', 's', 'w', 'k', 'p', 'i', 'p', 'z', 'f', 'g', 'g', 'b', 'm', 'm', 'd', 't', 'k', 'c', + 'h', 'd', 'w', 'q', 't', 'r', 'q', 'n', 'u', 'w', 'c', 'n', 'x', 'p', 'h', 't', 'z', 'w', 'd', 'q', + 'x', 'i', 'w', 'd', 'j', 'j', 'r', 'm', 'c', 'c', 'o', 'l', 'f', 'm', 'd', 'b', 'g', 't', 'q', 'a', + 'p', 's', 'q', 'p', 'j', 'x', 'i', 'k', 'w' }, + { 'm', 'a', 't', 'u', 'w', 'z', 'g', 'h', 'q', 's', 'l', 'u', 'j', 'y', 'c', 'v', 'e', 'f', 'b', 'z', + 'v', 'e', 'r', 'n', 'u', 'g', 'y', 'r', 'q', 'i', 'n', 'e', 'k', 'j', 'z', 'i', 'i', 'h', 's', 'a', + 'z', 'd', 'v', 'i', 'e', 'x', 'g', 'i', 'c', 'j', 'j', 'z', 'n', 'a', 'o', 'h', 'i', 'm', 'z', 'z', + 'w', 'o', 'd', 'g', 'x', 'e', 'q', 'h', 'l' }, + { 'i', 'k', 'n', 'o', 'f', 'v', 's', 'e', 'd', 'c', 'i', 'n', 'n', 'j', 'a', 'o', 'x', 'k', 'y', 'j', + 'l', 'x', 'f', 'b', 'd', 'n', 'b', 'j', 'd', 'i', 'x', 'o', 's', 'k', 'c', 'z', 'h', 'w', 'f', 'l', + 'a', 'p', 'a', 'n', 'y', 'c', 'd', 'v', 'm', 'c', 'g', 'z', 'b', 'l', 'b', 'g', 'a', 'c', 'q', 'g', + 'k', 'n', 'u', 'f', 'x', 'y', 'g', 'g', 'u' }, + { 'o', 'r', 'u', 'd', 'r', 'j', 'c', 'u', 'n', 'z', 'i', 'r', 'g', 'i', 'u', 't', 'j', 'b', 'u', 'l', + 't', 'c', 'x', 'g', 'w', 'e', 'f', 'u', 'l', 'l', 'q', 'z', 'f', 'm', 'e', 'w', 'v', 'g', 's', 'k', + 'g', 'b', 'd', 'o', 'j', 'y', 'h', 'u', 'z', 'd', 'f', 's', 'f', 'e', 'a', 'b', 'j', 'f', 'x', 'u', + 's', 'p', 'v', 'x', 'b', 'z', 'w', 'z', 'i' }, + { 'o', 'l', 'q', 'r', 'p', 'e', 'c', 'n', 'n', 'w', 'o', 'r', 'd', 'g', 'w', 'i', 'i', 'a', 'r', 'p', + 'z', 'n', 'h', 'p', 'k', 'h', 's', 'l', 'd', 't', 'v', 't', 'f', 'n', 'l', 'u', 'r', 'n', 'j', 'h', + 'm', 'x', 'y', 'p', 'f', 'u', 'z', 'n', 'w', 's', 'c', 'w', 'h', 'l', 'n', 'r', 's', 'h', 'f', 'v', + 'b', 'a', 'p', 'i', 'o', 'c', 'c', 'h', 'p' }, + { 'n', 'o', 'd', 'm', 'o', 'v', 'r', 'l', 'u', 'g', 'h', 'n', 'i', 'f', 'u', 't', 's', 'o', 'o', 'a', + 't', 'j', 'd', 'v', 'a', 'l', 'l', 'c', 'p', 't', 'u', 'c', 'j', 'z', 'o', 'y', 'u', 'h', 'j', 'p', + 'n', 's', 'e', 'x', 'y', 'y', 's', 's', 'o', 'g', 'u', 'j', 's', 'x', 'f', 'u', 'i', 'q', 'x', 'x', + 'l', 't', 'b', 'x', 's', 'r', 'x', 'n', 'y' }, + { 'i', 'e', 'n', 'a', 'i', 'm', 'a', 'i', 'h', 't', 'x', 'n', 'n', 'g', 'h', 'l', 'l', 'c', 'v', 'e', + 'b', 'v', 'r', 'w', 'w', 'o', 'o', 'q', 'l', 'd', 'q', 't', 'j', 'e', 'w', 'r', 's', 'w', 'a', 'z', + 'r', 'z', 'p', 'f', 'f', 'w', 's', 't', 'a', 'p', 'x', 'b', 'k', 'q', 'x', 'j', 'f', 'o', 'z', 's', + 't', 'p', 'o', 'd', 'v', 'k', 'w', 'o', 'g' }, + { 'y', 'p', 'a', 'x', 'e', 'f', 'd', 'd', 'z', 'y', 'd', 'o', 'x', 'h', 'b', 'n', 'g', 'k', 'u', 'w', + 'j', 'n', 'q', 'y', 'b', 'v', 'w', 'l', 'r', 'k', 't', 's', 'b', 't', 'p', 'g', 'a', 'u', 'l', 'b', + 's', 'q', 'q', 'p', 'x', 'r', 'd', 'e', 'd', 'x', 'a', 'm', 'm', 's', 'm', 'n', 'n', 'i', 'a', 'h', + 'u', 'w', 'b', 'w', 'p', 'q', 'e', 's', 'l' }, + { 'r', 't', 'd', 'h', 'l', 'v', 'f', 'e', 'a', 'l', 'h', 'z', 'l', 'v', 'm', 'g', 'i', 'b', 'v', 's', + 'c', 'e', 'n', 'y', 'f', 'l', 'p', 'w', 'p', 'h', 'j', 'g', 'd', 'o', 'n', 'o', 'j', 'u', 'v', 'l', + 'f', 'c', 'l', 't', 'y', 'x', 'z', 'i', 'y', 'u', 'a', 'c', 'z', 'p', 'a', 'g', 'a', 'q', 'c', 'r', + 'z', 'l', 'z', 'c', 'c', 'p', 't', 'l', 'j' }, + { 'o', 'x', 'p', 's', 'k', 'k', 's', 'h', 'j', 'a', 'h', 'f', 'b', 'k', 'g', 's', 'm', 'n', 't', 'c', + 'r', 'k', 'c', 'f', 'k', 'g', 'h', 'z', 'z', 'u', 'k', 'n', 't', 'z', 'g', 'd', 'l', 'y', 'm', 'w', + 'z', 'w', 'c', 'c', 'i', 'k', 'w', 'u', 'z', 'p', 'x', 'r', 'a', 'b', 'w', 'm', 'h', 'f', 'l', 'h', + 'z', 'x', 'w', 'v', 'x', 'c', 'y', 'k', 'd' }, + { 'n', 'h', 'e', 'j', 'j', 'i', 'r', 'v', 'e', 'l', 'v', 'u', 'k', 'm', 'w', 'n', 'k', 'i', 'v', 'r', + 'v', 'e', 's', 's', 'a', 'n', 'r', 'f', 'o', 'c', 'i', 'b', 'j', 'm', 'k', 'u', 'u', 'd', 'p', 'y', + 'o', 'm', 's', 'b', 'y', 'o', 'o', 'k', 'y', 'l', 'd', 't', 'r', 'w', 'm', 'u', 'j', 'f', 'z', 'x', + 'h', 'j', 'y', 's', 'v', 'k', 'o', 'p', 'n' }, + { 'e', 'p', 'e', 's', 'k', 'f', 't', 'a', 'v', 'd', 'z', 'j', 'h', 's', 'a', 'f', 'g', 'w', 'o', 'm', + 'v', 'o', 'v', 'e', 'o', 'o', 'b', 'z', 'e', 's', 'm', 'i', 'i', 's', 'd', 's', 'x', 'w', 'u', 'v', + 'z', 't', 'e', 'i', 'o', 'g', 'n', 'u', 'd', 'e', 'i', 'y', 's', 'g', 'f', 'g', 'w', 'g', 'h', 'a', + 'b', 'w', 'l', 'j', 'o', 'o', 'd', 'm', 'k' }, + { 'z', 'h', 'j', 't', 'n', 'u', 'h', 't', 'j', 'd', 'w', 'n', 'o', 'x', 'h', 'u', 'c', 'q', 'q', 'k', + 'x', 'q', 'n', 't', 'b', 'w', 'k', 'p', 'b', 'w', 'z', 'b', 'f', 'l', 'w', 'u', 'h', 'f', 'n', 'q', + 'i', 'k', 'g', 'w', 'j', 'p', 'q', 'n', 'f', 'g', 'x', 'f', 'z', 'l', 'a', 'a', 'j', 'k', 's', 'n', + 'i', 't', 'q', 'p', 'e', 'o', 'j', 'n', 't' }, + { 'x', 'g', 'b', 'j', 'm', 'a', 'u', 'b', 'q', 'h', 'j', 'z', 'e', 'q', 'y', 'r', 'q', 'a', 'd', 'b', + 'u', 's', 'l', 'o', 'i', 'b', 'u', 'w', 'k', 'i', 'r', 'j', 'o', 's', 'u', 'c', 's', 'o', 'f', 'l', + 'v', 'o', 'k', 'c', 'e', 'k', 't', 'x', 'm', 'y', 'a', 'h', 'q', 'l', 'v', 'a', 'm', 'r', 'y', 'z', + 'b', 'p', 'k', 'p', 'i', 'f', 't', 'c', 'v' }, + { 'z', 'n', 'r', 'p', 'z', 'v', 'w', 'j', 'q', 't', 'y', 'p', 'v', 'f', 'h', 'g', 'c', 'i', 'v', 't', + 'g', 'w', 'x', 'w', 'g', 'o', 'g', 'n', 'i', 'i', 'j', 'j', 'w', 'c', 'y', 'x', 'x', 'w', 'h', 'p', + 'r', 'h', 'e', 'm', 'm', 'm', 't', 'q', 'u', 'q', 'l', 'c', 'm', 'i', 'y', 's', 'x', 'e', 'i', 'f', + 'p', 'r', 'q', 'n', 'v', 'o', 'k', 'u', 'n' }, + { 'r', 'j', 'e', 'y', 'o', 'r', 'm', 'c', 'm', 'e', 'y', 'e', 'q', 'a', 'q', 'a', 'b', 'k', 'x', 'h', + 's', 'e', 'w', 'l', 'u', 'j', 'g', 'l', 'u', 'a', 'y', 'n', 'k', 'e', 'o', 'a', 'v', 'c', 'e', 'j', + 'h', 'c', 'n', 'z', 'e', 'd', 'z', 'h', 'q', 'x', 'p', 'i', 'd', 'n', 'w', 'a', 'z', 'c', 'l', 't', + 'f', 'l', 'i', 'r', 'p', 'y', 'r', 'n', 'b' }, + { 'x', 'w', 'k', 'z', 'm', 'j', 'f', 'r', 'k', 'n', 'h', 'j', 'e', 's', 'n', 'r', 'o', 'p', 'q', 'q', + 'a', 'l', 'v', 'n', 'u', 'o', 'e', 'u', 'f', 'r', 'x', 'c', 'q', 'h', 'd', 'c', 's', 'l', 't', 'd', + 'y', 'b', 'm', 'c', 't', 'b', 'v', 'j', 'q', 'm', 'z', 's', 'x', 'x', 'h', 't', 'l', 'm', 'q', 't', + 'd', 'n', 'x', 'v', 'x', 'd', 'x', 'r', 'o' }, + { 't', 'u', 'o', 'w', 'j', 's', 'r', 'm', 'n', 'a', 'f', 'z', 'z', 'x', 'z', 'y', 'h', 'u', 'm', 't', + 'm', 'h', 'y', 'a', 'g', 'u', 'z', 'l', 't', 'q', 'z', 'm', 'n', 'p', 'i', 'w', 'h', 'z', 'k', 'v', + 'z', 'p', 'w', 'b', 'p', 'x', 'b', 'w', 'u', 'n', 'r', 'g', 'w', 'p', 'i', 'd', 'j', 'h', 'o', 'd', + 'y', 'q', 'r', 'l', 'f', 'a', 'j', 'p', 'z' }, + { 'v', 'k', 'b', 'l', 'i', 'e', 'c', 'g', 'f', 'a', 'a', 'v', 'r', 'i', 'r', 'i', 'r', 'w', 'u', 'y', + 'n', 'z', 'y', 'f', 'q', 'j', 'm', 'q', 'u', 'b', 's', 'q', 'l', 'v', 'd', 'w', 'z', 'h', 'c', 'g', + 'j', 'e', 'b', 'c', 'm', 'v', 'k', 'd', 't', 'g', 'e', 'g', 'f', 'c', 'l', 'y', 'o', 'a', 'q', 'i', + 'b', 'k', 'a', 'p', 'f', 'f', 'l', 'g', 'o' }, + { 'n', 'n', 'x', 't', 'o', 'b', 'f', 'l', 'm', 'j', 'f', 'u', 'n', 'l', 'a', 'r', 'z', 'y', 'f', 'z', + 'q', 'q', 'c', 'b', 's', 'r', 'g', 'a', 'c', 'n', 'o', 'r', 'a', 'o', 'k', 'q', 'p', 'q', 'c', 'd', + 'b', 'h', 'y', 'q', 'u', 'a', 'h', 'v', 'a', 'n', 'w', 'q', 'f', 'z', 'r', 'x', 's', 'a', 'x', 'v', + 'n', 'o', 'o', 'p', 'c', 'z', 'h', 'r', 'r' }, + { 'l', 'x', 's', 'u', 'v', 'k', 'p', 'x', 'r', 'm', 'x', 'g', 'l', 'p', 'n', 'k', 'h', 'l', 'c', 'h', + 'k', 'z', 'w', 'y', 'o', 'n', 'a', 'n', 'u', 'u', 'g', 'g', 'r', 'a', 'a', 'o', 'k', 'r', 'n', 'd', + 'g', 'k', 'k', 'r', 'z', 'x', 'b', 'i', 'k', 'f', 'r', 'v', 'f', 'n', 'v', 'v', 'a', 'y', 'k', 'x', + 'u', 'q', 'd', 'n', 'q', 'f', 'b', 'a', 'z' }, + { 'o', 'd', 'f', 'a', 'p', 'y', 'b', 'p', 'b', 'm', 'b', 'g', 'd', 'y', 'n', 'r', 'u', 'k', 't', 's', + 'u', 'q', 'm', 'k', 'v', 'z', 'c', 'd', 'c', 'e', 'c', 's', 'k', 'j', 'u', 'b', 'h', 'x', 'q', 'k', + 'j', 'u', 's', 'n', 's', 'i', 'g', 'm', 's', 'z', 'g', 'n', 'q', 's', 'z', 'n', 't', 'e', 'q', 'x', + 'i', 's', 'p', 's', 'd', 'l', 'u', 'm', 'j' }, + { 'm', 'w', 's', 'g', 'p', 'h', 'z', 'x', 'n', 'n', 'r', 'p', 'u', 'g', 'f', 'o', 'g', 's', 'i', 'k', + 'j', 'h', 'u', 'd', 'z', 'n', 'h', 'k', 'j', 'v', 't', 'v', 's', 'o', 'c', 'j', 'v', 'd', 'g', 'l', + 'q', 'z', 'a', 'k', 'g', 'h', 'z', 'm', 'z', 'j', 'y', 'k', 'q', 's', 'q', 'r', 'h', 'z', 'c', 'q', + 'u', 'x', 'm', 'm', 'l', 'q', 'v', 'j', 't' }, + { 'd', 'u', 'j', 'd', 'u', 'w', 'j', 'b', 'v', 'x', 'c', 'g', 'x', 'p', 'y', 'r', 'f', 'q', 'z', 'g', + 's', 'p', 'a', 'r', 'd', 'p', 'd', 't', 'k', 'o', 'o', 'q', 'i', 'y', 't', 'e', 'u', 'e', 'h', 'r', + 'd', 'l', 'z', 'a', 'a', 'x', 'r', 'h', 'n', 'q', 'n', 'h', 'i', 'q', 'b', 'n', 'f', 'g', 'j', 'r', + 'u', 'x', 'h', 'e', 'x', 'c', 'i', 't', 'i' }, + { 'r', 'm', 'l', 'c', 'l', 'l', 'f', 'j', 'f', 'm', 'y', 'x', 'c', 'i', 'h', 'u', 'j', 'v', 'z', 'p', + 'e', 'q', 'j', 'b', 'a', 'n', 'b', 'c', 'x', 'u', 'l', 'o', 'j', 'y', 's', 'u', 'm', 'x', 'f', 'r', + 'm', 'e', 'o', 'q', 'm', 'w', 'k', 'v', 't', 'j', 'm', 'x', 'b', 'v', 'a', 'b', 'k', 'd', 'g', 'j', + 'a', 't', 'z', 'j', 'r', 'r', 'd', 'd', 'p' }, + { 'j', 'w', 'b', 'n', 'n', 'r', 'b', 'l', 'b', 'y', 'e', 'm', 'k', 'b', 'p', 'h', 'd', 't', 't', 'h', + 'z', 'c', 'h', 'u', 'b', 'q', 'n', 's', 'v', 'r', 'j', 'e', 'p', 'k', 't', 'c', 'd', 'w', 'n', 'g', + 'w', 'r', 'u', 'i', 'u', 'k', 'p', 'a', 'd', 'k', 'h', 'e', 'o', 'q', 'y', 'p', 'i', 'l', 'k', 'd', + 'e', 't', 'k', 'u', 'g', 'd', 'y', 'l', 'c' }, + { 'm', 'u', 'y', 'f', 'o', 'h', 'c', 'y', 'y', 'c', 'b', 'j', 'l', 'h', 'x', 'b', 'f', 'p', 'j', 't', + 'b', 'm', 'x', 'u', 'y', 't', 'c', 'c', 's', 'q', 'g', 'g', 'k', 'e', 'n', 'y', 'n', 'p', 'z', 'm', + 't', 'a', 'x', 'e', 'i', 'u', 'f', 'p', 'l', 'q', 'i', 'm', 'f', 'g', 'j', 'd', 'b', 'n', 'h', 't', + 'd', 'n', 'b', 'p', 'u', 'p', 'o', 'h', 'g' }, + { 'n', 'v', 'a', 'p', 's', 'e', 'z', 'p', 'm', 'r', 'a', 'e', 'z', 'n', 'j', 'h', 'y', 'p', 'j', 'l', + 'w', 'e', 'r', 'm', 'i', 'g', 'g', 'z', 'w', 'p', 'f', 'l', 'l', 'h', 'b', 'd', 'm', 'c', 's', 'a', + 't', 'v', 'e', 'v', 'k', 'q', 'c', 'i', 'f', 'n', 'v', 'd', 'u', 'm', 'p', 'c', 'v', 'x', 'b', 'r', + 'n', 'i', 'f', 'y', 'q', 'g', 'd', 'e', 'k' }, + { 'y', 'g', 'e', 't', 'm', 'z', 'f', 'c', 'd', 'n', 'j', 'r', 'k', 'n', 'n', 'z', 'c', 'p', 'w', 'c', + 's', 'n', 'p', 'a', 'u', 'p', 's', 'c', 's', 'w', 'n', 's', 'e', 't', 'l', 'r', 'u', 'q', 't', 'x', + 'f', 'd', 'q', 's', 's', 'd', 'r', 'w', 'u', 'n', 'y', 'm', 'c', 'p', 'p', 'x', 'e', 'h', 'z', 'z', + 'g', 'o', 'r', 'k', 'h', 'f', 'b', 'b', 'v' }, + { 'x', 'b', 'd', 'a', 'r', 'x', 'u', 'v', 'o', 'q', 'p', 'd', 'r', 'e', 'h', 'g', 't', 'g', 'l', 'a', + 'i', 'm', 'i', 'w', 'd', 't', 'e', 'i', 'u', 'h', 'g', 'r', 'i', 'l', 't', 'a', 'i', 'p', 'x', 'y', + 'g', 'o', 'd', 'z', 'u', 'k', 'f', 'n', 't', 's', 'q', 'b', 'e', 'y', 'x', 'k', 't', 'd', 's', 'q', + 'n', 'a', 'h', 'v', 'l', 'd', 'x', 't', 's' }, + { 'w', 't', 'a', 'l', 'w', 'z', 'h', 'j', 'h', 'v', 'c', 'z', 'n', 'd', 'g', 'n', 'c', 'q', 'j', 'g', + 'i', 'z', 'v', 'j', 'i', 'q', 'w', 'l', 'o', 's', 'g', 'm', 'l', 'i', 'x', 'k', 'k', 'h', 't', 'r', + 'e', 'v', 's', 'r', 'a', 'y', 'g', 'c', 'o', 'p', 'k', 'z', 'q', 'f', 'k', 'z', 'y', 'i', 'm', 'm', + 'a', 's', 'y', 'm', 'd', 'y', 'w', 'n', 'h' }, + { 'r', 'e', 'l', 'm', 'w', 'e', 'o', 'x', 'k', 'q', 'n', 'c', 'd', 'm', 's', 'k', 'y', 't', 'i', 'h', + 'g', 'w', 'h', 'a', 'x', 'v', 'd', 'x', 't', 'q', 'e', 'k', 'w', 'p', 'y', 'v', 'v', 'o', 's', 'f', + 'h', 'f', 'j', 'k', 'u', 'e', 'u', 'u', 'x', 'f', 'b', 'd', 'b', 'l', 'e', 'a', 'i', 'j', 'x', 'e', + 'a', 'd', 'q', 'y', 'u', 'p', 't', 'p', 'd' }, + { 'n', 'v', 'm', 'v', 'g', 'w', 'p', 'k', 't', 'l', 'k', 'a', 'p', 'n', 'b', 'c', 't', 'e', 'k', 'd', + 'b', 'o', 'f', 'h', 'f', 'd', 'b', 'w', 'z', 'r', 'b', 'm', 'o', 'o', 'h', 'u', 'm', 'y', 'h', 'h', + 'k', 'r', 'h', 'z', 'g', 'l', 'd', 'a', 'p', 'n', 'f', 's', 'e', 'k', 'z', 'l', 'p', 'b', 'j', 'o', + 'u', 'm', 'd', 'i', 'c', 'k', 'e', 'p', 'l' }, + { 'l', 'w', 'x', 'e', 'e', 'y', 'l', 'r', 'b', 'n', 'g', 'q', 's', 'a', 'u', 'c', 'a', 'h', 't', 'd', + 'q', 'i', 'x', 'd', 'l', 'h', 'f', 'x', 'l', 'w', 'k', 'x', 'v', 'h', 'b', 'b', 'f', 'o', 's', 'i', + 'b', 'a', 'z', 't', 'a', 'v', 'x', 'a', 'd', 'r', 'f', 'v', 'b', 'e', 'y', 'm', 'l', 'g', 'l', 'x', + 'e', 'w', 'u', 'z', 'f', 'x', 'c', 'n', 'm' }, + { 'u', 'v', 'p', 'u', 'w', 'j', 'x', 's', 'i', 'x', 'v', 'z', 'f', 'q', 'a', 'j', 'r', 'o', 'v', 'z', + 'c', 's', 'f', 'y', 'o', 'h', 'f', 'n', 'j', 's', 'b', 'g', 'q', 'r', 'c', 'm', 'a', 'z', 'e', 'i', + 'x', 'b', 'k', 'e', 's', 'm', 'n', 'l', 'd', 'i', 'm', 'f', 'c', 'r', 'f', 's', 'y', 'k', 'g', 'i', + 'f', 'h', 'o', 'v', 'y', 's', 'h', 'a', 's' }, + { 'm', 'l', 'r', 'p', 'v', 'v', 'j', 'j', 'i', 'u', 'm', 't', 'i', 'r', 'x', 'a', 'y', 'q', 'a', 'j', + 'w', 'i', 'o', 'f', 'y', 'l', 'e', 'r', 's', 'g', 'j', 'g', 'r', 'a', 'w', 'o', 'x', 'f', 'y', 'f', + 'c', 'k', 'a', 'k', 'e', 'y', 'm', 'c', 'q', 'n', 'n', 'o', 'x', 'd', 't', 'w', 'o', 'z', 'p', 'j', + 'g', 'a', 'p', 'x', 'c', 'n', 'm', 'z', 't' }, + { 'm', 'g', 'x', 'w', 'j', 'h', 'c', 'j', 'u', 'f', 'z', 'j', 's', 'n', 'g', 'y', 'i', 'c', 'm', 'i', + 't', 'v', 'q', 'v', 'n', 'n', 'z', 'a', 'b', 'a', 'v', 'n', 'j', 's', 'm', 's', 'c', 'o', 'b', 'y', + 't', 'c', 'h', 'o', 'p', 'n', 'o', 'x', 's', 'c', 'h', 'n', 'y', 'x', 'j', 'n', 'n', 'k', 'n', 'q', + 'l', 'l', 'e', 'u', 'd', 's', 'm', 'h', 'g' }, + { 'p', 'f', 'c', 'r', 'o', 's', 'i', 'c', 'g', 'h', 'w', 'i', 'p', 'j', 'i', 'o', 'u', 'v', 'b', 'f', + 'l', 'u', 'q', 'w', 'y', 'k', 'b', 's', 'y', 'l', 'y', 'n', 's', 'a', 'g', 'h', 'u', 'o', 'l', 'a', + 'v', 'h', 'l', 'm', 'q', 't', 'b', 'n', 'r', 'e', 's', 'e', 'y', 'i', 'c', 'y', 'u', 'f', 'q', 'u', + 'q', 'r', 'j', 'j', 't', 'p', 's', 'o', 'f' }, + { 'd', 'o', 'a', 'k', 'b', 'p', 'c', 'v', 'q', 'p', 'o', 'w', 'j', 'u', 'x', 't', 'w', 'v', 'p', 'b', + 'o', 'j', 'u', 'f', 'u', 'd', 'y', 'j', 'v', 'm', 'q', 'a', 'd', 't', 'k', 'e', 'i', 'o', 'b', 'a', + 'g', 'r', 'w', 'p', 'l', 't', 'l', 'h', 'r', 'a', 'l', 'h', 'k', 'f', 'm', 'g', 'k', 'm', 'q', 'h', + 'z', 'i', 'h', 'e', 'b', 't', 'k', 'j', 'j' }, + { 'o', 'j', 'r', 'f', 'i', 'h', 't', 'd', 'u', 'a', 'w', 'u', 'n', 'd', 'g', 'u', 'p', 'n', 'e', 'e', + 'f', 'n', 'd', 'n', 'w', 'j', 'p', 'p', 't', 'a', 'b', 'h', 'm', 's', 'p', 'u', 'b', 'i', 'z', 'v', + 'k', 'w', 's', 'y', 'b', 'y', 's', 't', 'n', 'z', 'x', 's', 'o', 'c', 'i', 'l', 'l', 'z', 'c', 'e', + 'z', 'd', 'o', 'n', 'w', 'd', 'j', 'z', 'l' }, + { 'j', 'v', 'v', 'h', 'n', 'v', 'k', 'n', 'q', 'd', 'b', 'p', 'a', 'v', 'd', 'e', 'f', 'o', 'p', 'g', + 'r', 'w', 'i', 'w', 'm', 'v', 's', 'p', 'f', 's', 'c', 'q', 'p', 'z', 'z', 'e', 'v', 'j', 'r', 'l', + 'n', 'u', 'c', 'p', 's', 'f', 'u', 'x', 'w', 'j', 'e', 'p', 'h', 'm', 'l', 't', 'j', 'g', 'k', 'o', + 'a', 'm', 'g', 'p', 'm', 'f', 't', 'j', 'p' }, + { 'm', 'u', 'c', 'h', 'w', 't', 'z', 'd', 'n', 'y', 'b', 'z', 'e', 'q', 'g', 's', 'e', 'c', 'c', 'k', + 'm', 's', 'k', 'z', 'z', 'b', 'n', 'e', 'u', 'w', 'v', 'g', 's', 'z', 'p', 'o', 't', 'o', 't', 'i', + 'p', 'v', 'h', 't', 'n', 'q', 'o', 'r', 's', 'q', 'd', 'e', 'k', 'p', 'd', 'l', 'q', 's', 'q', 'k', + 'o', 'n', 't', 'g', 'n', 'i', 'w', 'g', 'z' }, + { 's', 'q', 'o', 'n', 'y', 'j', 'c', 'o', 'z', 'w', 'i', 'p', 'z', 'm', 'c', 'p', 's', 'n', 'h', 'k', + 'f', 's', 'z', 't', 'n', 'h', 'i', 'v', 'e', 'q', 'w', 'w', 'g', 'm', 'l', 'e', 'y', 'p', 'u', 'x', + 'l', 'e', 'p', 'n', 'r', 'r', 'e', 'l', 'e', 'n', 'v', 'm', 'f', 'w', 'f', 'u', 'g', 'n', 's', 'k', + 'd', 'o', 'i', 'j', 'b', 'v', 'q', 'b', 'm' }, + { 'm', 'y', 'a', 'r', 'n', 'p', 'k', 'g', 't', 'v', 'n', 'g', 's', 'z', 'o', 'p', 'g', 'k', 'v', 't', + 'c', 'h', 'y', 'r', 'r', 'j', 'u', 'o', 'b', 'x', 'a', 'o', 'v', 'a', 'h', 'l', 'p', 'r', 'r', 'k', + 'o', 'e', 't', 'g', 'f', 'j', 'x', 'l', 't', 'u', 'g', 'w', 'd', 'g', 'p', 'w', 'q', 'l', 'k', 't', + 'i', 'l', 'h', 'f', 'l', 'q', 'q', 'd', 'j' }, + { 'k', 'n', 'x', 'o', 'g', 'g', 'w', 'p', 'f', 'h', 'l', 'c', 'o', 'j', 'f', 'u', 'y', 'c', 'm', 'l', + 'o', 'g', 'v', 'z', 'p', 'a', 'n', 'g', 't', 'q', 'r', 'd', 'f', 'r', 't', 'o', 'x', 'p', 'f', 'e', + 'x', 'q', 'g', 'n', 'z', 'm', 'j', 'z', 'q', 'w', 'k', 'e', 'e', 'h', 'g', 't', 'i', 'v', 'b', 'b', + 'n', 'v', 'g', 's', 'm', 'z', 'g', 'l', 'p' }, + { 'o', 'p', 'o', 'e', 'w', 'b', 'g', 'k', 'k', 'f', 'a', 'i', 's', 'g', 'o', 'b', 'o', 'k', 'j', 'j', + 'n', 'm', 'w', 'i', 's', 'r', 'u', 'u', 'x', 'f', 'j', 'n', 'v', 'x', 'u', 't', 'a', 'a', 'd', 'm', + 'h', 'f', 'x', 'b', 'n', 'l', 'd', 'c', 'v', 'o', 'n', 'j', 'd', 'k', 'r', 'x', 'b', 'o', 'r', 'a', + 'v', 'c', 'o', 's', 'b', 'i', 'l', 'b', 'k' }, + { 'q', 'q', 'r', 'v', 'n', 'v', 'j', 'y', 'y', 'n', 'u', 'o', 'a', 'f', 'r', 'm', 'w', 'p', 'p', 'm', + 'g', 'q', 'i', 'j', 'g', 'a', 'k', 'q', 'o', 'o', 'c', 'e', 'e', 'v', 'c', 'r', 'q', 'n', 'r', 'q', + 'a', 'l', 'f', 'c', 's', 'w', 'p', 'p', 'n', 'e', 'd', 'w', 'w', 'l', 'f', 'e', 'o', 'r', 'w', 'e', + 'f', 'y', 'i', 'l', 'u', 'k', 'c', 'k', 'x' }, + { 'u', 'd', 'z', 'h', 'i', 'c', 'a', 'g', 't', 'r', 'w', 'z', 'u', 's', 'y', 'i', 'x', 'e', 'y', 'q', + 'b', 'c', 'y', 'b', 'm', 'j', 'v', 'z', 'o', 'i', 'y', 'i', 'l', 'a', 'r', 'v', 'e', 't', 'd', 'x', + 'k', 'z', 'y', 'h', 'r', 'y', 'r', 'q', 'd', 'r', 'j', 'g', 'v', 'h', 'h', 'h', 'q', 'd', 'i', 'e', + 'n', 'j', 'o', 'a', 'j', 'i', 'x', 'n', 'b' }, + { 'a', 'm', 'm', 'c', 'k', 'v', 'v', 'j', 'm', 'm', 'o', 'd', 'x', 'u', 'y', 'e', 'b', 'h', 'w', 'g', + 's', 'b', 't', 'b', 'r', 'v', 'k', 'z', 's', 'z', 'd', 't', 'l', 'p', 'x', 'x', 'm', 's', 'i', 'a', + 'g', 'w', 'f', 'd', 's', 'd', 'j', 'u', 'm', 'g', 'c', 'e', 'j', 'y', 'h', 'a', 't', 't', 'c', 'o', + 's', 'f', 'j', 'f', 'w', 'g', 'd', 'i', 'y' }, + { 'n', 'k', 'h', 'm', 'p', 'k', 'e', 'u', 'u', 'a', 'g', 'a', 'f', 'n', 'j', 'd', 'w', 'l', 'y', 'q', + 'n', 'm', 'i', 'u', 'v', 'q', 'q', 'd', 't', 'a', 'c', 'i', 'k', 'j', 'u', 'b', 'v', 'b', 'v', 'r', + 'b', 'e', 'r', 'i', 'r', 'c', 'l', 'n', 'q', 'm', 'f', 'f', 'y', 'o', 'a', 'w', 'e', 's', 'z', 'z', + 'v', 'd', 'j', 'h', 'o', 'e', 'j', 'm', 'h' }, + { 'e', 'd', 'i', 'k', 'x', 't', 'b', 'z', 'e', 'r', 'r', 's', 'w', 'x', 't', 'm', 'z', 'p', 's', 'r', + 'q', 't', 'o', 'w', 'd', 'w', 'm', 'j', 'h', 'y', 'q', 'n', 'c', 'a', 'y', 'b', 't', 'b', 'c', 'a', + 'u', 'u', 's', 't', 'r', 'n', 'h', 's', 'e', 'a', 'l', 'x', 'v', 'a', 't', 'a', 'y', 'h', 'l', 'h', + 'g', 'd', 'u', 'k', 'e', 's', 'l', 'z', 'w' }, + { 'n', 'b', 'q', 'j', 'w', 'j', 'c', 'j', 'r', 'u', 'o', 't', 'g', 'l', 'q', 'i', 'g', 'r', 'i', 'n', + 'e', 'p', 'v', 'i', 'j', 'h', 'o', 'e', 'u', 'n', 'a', 'i', 'r', 's', 't', 'n', 'c', 'w', 'y', 'v', + 's', 'o', 'q', 'a', 'b', 'g', 'i', 'h', 'z', 's', 'x', 'e', 'h', 'u', 'o', 't', 'c', 'c', 'x', 'w', + 'r', 'z', 'g', 'i', 'r', 'a', 'x', 'v', 'y' }, + { 'y', 'q', 'q', 'm', 'g', 'r', 'o', 'p', 'b', 'x', 'o', 'u', 'u', 'u', 'b', 'p', 'i', 'w', 'r', 'k', + 't', 'p', 'c', 'u', 'w', 'm', 'm', 'y', 'k', 'j', 'w', 'i', 'a', 'o', 'w', 'i', 'f', 'k', 'x', 'j', + 'i', 'o', 'd', 'e', 'i', 'g', 't', 'r', 'd', 'm', 'b', 'y', 'c', 'f', 't', 'a', 's', 'h', 'y', 'e', + 'q', 'w', 'o', 's', 'k', 'k', 'd', 's', 'v' }, + { 'a', 'b', 'f', 'o', 'g', 'j', 'z', 'm', 'f', 'q', 'p', 'r', 'r', 'o', 'v', 'z', 'j', 'x', 'r', 'q', + 'v', 'v', 'i', 't', 'j', 'b', 'g', 'v', 'e', 'y', 's', 'g', 'b', 'x', 'x', 'h', 'j', 'w', 't', 'o', + 'm', 'l', 'h', 'f', 'b', 'd', 'e', 'k', 'a', 'x', 'c', 'y', 's', 'k', 't', 'd', 'n', 'z', 'b', 'r', + 'z', 't', 'a', 'a', 'r', 'x', 'h', 'c', 'v' }, + { 'd', 'q', 'j', 'q', 'x', 'o', 'r', 'c', 'v', 'd', 'd', 's', 'h', 'd', 'n', 'r', 'w', 's', 'f', 'w', + 't', 'y', 'x', 'n', 'a', 'y', 'g', 'x', 'h', 'i', 'u', 'm', 'y', 'd', 'c', 'x', 's', 't', 'a', 'p', + 'y', 'f', 'h', 'f', 'i', 'w', 'z', 'g', 'p', 'g', 'c', 'i', 'g', 'a', 'x', 'h', 'a', 'd', 'g', 'h', + 'n', 'b', 'w', 'n', 'e', 'y', 'l', 'y', 'u' }, + { 'n', 'n', 's', 's', 'x', 'a', 'c', 't', 'z', 'i', 'k', 'f', 'n', 't', 'n', 'p', 's', 'u', 'p', 'w', + 'b', 'y', 'l', 'c', 'u', 'z', 'i', 'v', 'm', 'h', 'p', 'z', 'w', 'j', 't', 't', 'j', 'v', 'p', 'k', + 'f', 'z', 'r', 's', 'u', 'f', 'h', 'n', 'z', 'y', 'l', 'c', 'z', 'w', 'e', 't', 'x', 'p', 'o', 'j', + 'w', 'f', 'k', 'u', 'p', 'f', 'o', 'a', 'a' }, + { 'f', 'l', 'i', 'e', 'c', 'a', 'z', 'h', 'k', 'o', 'j', 'i', 'z', 'l', 'h', 'v', 's', 'd', 'v', 'j', + 't', 'e', 'f', 'z', 'r', 'b', 'q', 'w', 'r', 'q', 'z', 'w', 'b', 'h', 'b', 'g', 'h', 'c', 'p', 't', + 'q', 'a', 'c', 'r', 'm', 'j', 'o', 'g', 'o', 'j', 'p', 'i', 'q', 'w', 'j', 'h', 'x', 'z', 'd', 'p', + 'p', 'e', 'n', 't', 'l', 'o', 'z', 'v', 's' }, + { 'q', 'o', 'k', 'r', 's', 'b', 'f', 'c', 'q', 'l', 's', 'b', 'c', 'a', 'r', 'a', 'j', 'y', 'z', 'i', + 'e', 'o', 'a', 'i', 'e', 't', 'w', 's', 'u', 'r', 'n', 'k', 'h', 'x', 'd', 'a', 'b', 'i', 'e', 't', + 'v', 'w', 'u', 'z', 'z', 'm', 'z', 'i', 'm', 'z', 't', 'q', 'p', 't', 'b', 't', 'o', 'x', 'o', 'i', + 'q', 'd', 'u', 'x', 'c', 'a', 'z', 'd', 'i' }, + { 'd', 'y', 'g', 'c', 't', 'f', 'b', 'h', 'h', 'j', 't', 'g', 'e', 'm', 'x', 'x', 'n', 't', 'n', 'k', + 'j', 'x', 'c', 'm', 's', 'b', 'q', 's', 'b', 'u', 'c', 'g', 'u', 'i', 'i', 'n', 'q', 'j', 'u', 'x', + 'v', 'q', 'f', 'z', 'c', 'c', 'z', 'r', 'x', 'm', 'd', 'i', 'k', 'f', 'u', 'e', 'g', 'n', 'y', 'h', + 'h', 'c', 'q', 'b', 'l', 'y', 'r', 'd', 'k' } }; + + Assertions.assertFalse(demo.exist(board2, "afdfghjhy")); + + char[][] board3 = + { { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }, + { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'b' } }; + String longWord = + "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + + Assertions.assertTrue(demo.exist(board3, longWord)); + } + + public boolean exist(char[][] board, String word) { + if (board == null || board.length == 0 + || word == null || word.length() == 0) { return false; } + + int row = board.length; + int col = board[0].length; + boolean[][] visited = new boolean[row][col]; + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + if (dfs(board, visited, "", word, i, j)) { + return true; + } + } + } + + return false; + } + + // DFS + 回溯 + 剪枝 + public boolean dfs(char[][] board, boolean[][] visited, String str, String word, int x, int y) { + // 数组越界,扫描退出 + if (x < 0 || x >= board.length || y < 0 || y >= board[0].length) { return false; } + if (str.length() > word.length()) return false; + if (!word.startsWith(str)) return false; + // 已扫描过,则退出 + if (visited[x][y]) return false; + + // 拼接字符串,然后在字典树中查找,如果找到 word,添加到目标 list + // visited[x][y] 已被扫描过,置为 true + str += board[x][y]; + // if (!trie.startsWith(str)) return false; + if (word.equals(str)) return true; + visited[x][y] = true; + + // 基于当前位置,向四个方向展开 DFS 搜索 + boolean flag = dfs(board, visited, str, word, x + 1, y) + || dfs(board, visited, str, word, x - 1, y) + || dfs(board, visited, str, word, x, y + 1) + || dfs(board, visited, str, word, x, y - 1); + if (flag) return true; + + // // 重置 visited[x][y] 为 false + visited[x][y] = false; + return false; + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242II.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242II.java" new file mode 100644 index 0000000..3ad1f6e --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\215\225\350\257\215\346\220\234\347\264\242II.java" @@ -0,0 +1,161 @@ +package io.github.dunwu.algorithm.trie; + +import org.junit.jupiter.api.Assertions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Zhang Peng + * @see 212. 单词搜索 II + * @since 2020-07-04 + */ +public class 单词搜索II { + + Set set = new HashSet<>(); + + public static void main(String[] args) { + String[] words = { "oath", "pea", "eat", "rain" }; + char[][] board = { + { 'o', 'a', 'a', 'n' }, + { 'e', 't', 'a', 'e' }, + { 'i', 'h', 'k', 'r' }, + { 'i', 'f', 'l', 'v' } + }; + + 单词搜索II demo = new 单词搜索II(); + List result = demo.findWords(board, words); + Assertions.assertArrayEquals(Arrays.asList("oath", "eat").toArray(), result.toArray()); + } + + // 利用字典树来进行单词搜索 + public List findWords(char[][] board, String[] words) { + + if (board == null || board.length == 0 + || words == null || words.length == 0) { return new ArrayList<>(set); } + + // 初始化字典树 + Trie trie = new Trie(); + for (String w : words) { + trie.insert(w); + } + + int row = board.length; + int col = board[0].length; + boolean[][] visited = new boolean[row][col]; + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + dfs(board, visited, trie, "", i, j); + } + } + + return new ArrayList<>(set); + } + + public void dfs(char[][] board, boolean[][] visited, Trie trie, String str, int x, int y) { + // 数组越界,扫描退出 + if (x < 0 || x >= board.length || y < 0 || y >= board[0].length) { return;} + // 已扫描过,则退出 + if (visited[x][y]) return; + + // 拼接字符串,然后在字典树中查找,如果找到 word,添加到目标 list + // visited[x][y] 已被扫描过,置为 true + str += board[x][y]; + if (!trie.startsWith(str)) return; + if (trie.search(str)) set.add(str); + visited[x][y] = true; + + // 基于当前位置,向四个方向展开 DFS 搜索 + dfs(board, visited, trie, str, x + 1, y); + dfs(board, visited, trie, str, x - 1, y); + dfs(board, visited, trie, str, x, y + 1); + dfs(board, visited, trie, str, x, y - 1); + + // 重置 visited[x][y] 为 false + visited[x][y] = false; + } + + public static class Trie { + + private TrieNode root; + public static final int MAX_WORD_COUNT = 26; + + public Trie() { + root = new TrieNode('/'); + } + + public void insert(String word) { + if (word == null || word.length() == 0) { + return; + } + + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + int index = c - 'a'; + if (node.children[index] == null) { + node.children[index] = new TrieNode(c); + } + node = node.children[index]; + } + node.isEnd = true; + } + + public boolean search(String word) { + if (word == null || word.length() == 0) { + return false; + } + + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + int index = c - 'a'; + if (node.children[index] == null) { + return false; + } + node = node.children[index]; + } + return node.isEnd; + } + + public boolean startsWith(String prefix) { + if (prefix == null || prefix.length() == 0) { + return false; + } + + TrieNode node = root; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + int index = c - 'a'; + if (node.children[index] == null) { + return false; + } + node = node.children[index]; + } + return true; + } + + static class TrieNode { + + boolean isEnd; + char data; + TrieNode[] children; + + public TrieNode(char data) { + this(data, false); + } + + public TrieNode(char data, boolean isEnd) { + this.data = data; + this.isEnd = isEnd; + children = new TrieNode[MAX_WORD_COUNT]; + } + + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\256\236\347\216\260Trie_\345\211\215\347\274\200\346\240\221.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\256\236\347\216\260Trie_\345\211\215\347\274\200\346\240\221.java" new file mode 100644 index 0000000..4b1b84c --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\345\256\236\347\216\260Trie_\345\211\215\347\274\200\346\240\221.java" @@ -0,0 +1,99 @@ +package io.github.dunwu.algorithm.trie; + +import org.junit.jupiter.api.Assertions; + +/** + * @author Zhang Peng + * @see 208. 实现 Trie (前缀树) + * @since 2020-06-15 + */ +public class 实现Trie_前缀树 { + + public static void main(String[] args) { + 实现Trie_前缀树 trie = new 实现Trie_前缀树(); + trie.insert("apple"); + Assertions.assertTrue(trie.search("apple")); + Assertions.assertFalse(trie.search("app")); + Assertions.assertTrue(trie.startsWith("app")); + + trie.insert("app"); + Assertions.assertTrue(trie.search("app")); + } + + private TrieNode root; + public static final int MAX_WORD_COUNT = 26; + + public 实现Trie_前缀树() { + root = new TrieNode('/'); + } + + public void insert(String word) { + if (word == null || word.length() == 0) { + return; + } + + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + int index = c - 'a'; + if (node.children[index] == null) { + node.children[index] = new TrieNode(c); + } + node = node.children[index]; + } + node.isEnd = true; + } + + public boolean search(String word) { + if (word == null || word.length() == 0) { + return false; + } + + TrieNode node = root; + for (int i = 0; i < word.length(); i++) { + char c = word.charAt(i); + int index = c - 'a'; + if (node.children[index] == null) { + return false; + } + node = node.children[index]; + } + return node.isEnd; + } + + public boolean startsWith(String prefix) { + if (prefix == null || prefix.length() == 0) { + return false; + } + + TrieNode node = root; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + int index = c - 'a'; + if (node.children[index] == null) { + return false; + } + node = node.children[index]; + } + return true; + } + + public static class TrieNode { + + boolean isEnd; + char data; + TrieNode[] children; + + public TrieNode(char data) { + this(data, false); + } + + public TrieNode(char data, boolean isEnd) { + this.data = data; + this.isEnd = isEnd; + children = new TrieNode[MAX_WORD_COUNT]; + } + + } + +} diff --git "a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" new file mode 100644 index 0000000..8cd0187 --- /dev/null +++ "b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/trie/\346\234\200\351\225\277\345\205\254\345\205\261\345\211\215\347\274\200.java" @@ -0,0 +1,21 @@ +package io.github.dunwu.algorithm.trie; + +/** + * @author Zhang Peng + * @since 2020-06-15 + */ +public class 最长公共前缀 { + + public static void main(String[] args) { + longestCommonPrefix("flower", "flow", "flight"); + } + + public static String longestCommonPrefix(String... strs) { + Trie trie = new Trie(); + for (String s : strs) { + trie.insert(s.toCharArray()); + } + return trie.longest(); + } + +} diff --git a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/util/ArrayUtil.java b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/util/ArrayUtil.java index aa057d9..117d1b1 100644 --- a/codes/algorithm/src/main/java/io/github/dunwu/algorithm/util/ArrayUtil.java +++ b/codes/algorithm/src/main/java/io/github/dunwu/algorithm/util/ArrayUtil.java @@ -1,36 +1,97 @@ -package io.github.dunwu.ds.util; +package io.github.dunwu.algorithm.util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Random; /** + * 数组工具类 + * * @author Zhang Peng */ +@Slf4j public class ArrayUtil { - private static final Logger logger = LoggerFactory.getLogger(ArrayUtil.class); - public static void debugLogArray(T[] list, int begin, int end, String tip) { - String content = tip + getArrayString(list, begin, end); - if (logger.isDebugEnabled()) { - logger.debug(content); + public static int[] toIntArray(List list) { + if (list == null || list.isEmpty()) { return new int[0]; } + int[] res = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + res[i] = list.get(i); } + return res; } - public static String getArrayString(T[] list, int begin, int end) { - StringBuilder sb = new StringBuilder(); - sb.append("\n"); - for (int i = 0; i < begin; i++) { - sb.append("\t"); + public static List> toIntMatrixList(int[][] arr) { + if (arr == null || arr.length == 0) { return new ArrayList<>(); } + List> listlist = new ArrayList<>(); + for (int i = 0; i < arr.length; i++) { + List list = new ArrayList<>(); + listlist.add(list); + for (int j = 0; j < arr[i].length; j++) { + list.add(arr[i][j]); + } + } + return listlist; + } + + public static int[][] toIntMatrixArray(List> listlist) { + if (listlist == null || listlist.size() == 0) { return new int[0][0]; } + List arrList = new ArrayList<>(); + for (List list : listlist) { + arrList.add(toIntArray(list)); + } + return arrList.toArray(new int[listlist.size()][]); + } + + public static String[] toStringArray(List list) { + if (list == null || list.isEmpty()) { return new String[0]; } + String[] res = new String[list.size()]; + for (int i = 0; i < list.size(); i++) { + res[i] = list.get(i); + } + return res; + } + + public static List> toStringMatrixList(String[][] arr) { + if (arr == null || arr.length == 0) { return new ArrayList<>(); } + List> listlist = new ArrayList<>(); + for (String[] strings : arr) { + List list = new ArrayList<>(); + listlist.add(list); + Collections.addAll(list, strings); } + return listlist; + } + + public static String[][] toStringMatrixArray(List> listlist) { + if (listlist == null || listlist.size() == 0) { return new String[0][0]; } + List arrList = new ArrayList<>(); + for (List list : listlist) { + arrList.add(toStringArray(list)); + } + return arrList.toArray(new String[listlist.size()][]); + } + + public static void printArray(T[] arr, int begin, int end, String tip) { + System.out.printf("%s -> %s\n", tip, getArrayString(arr, begin, end)); + } + + public static String getArrayString(T[] arr) { + return getArrayString(arr, 0, arr.length); + } + + public static String getArrayString(T[] arr, int begin, int end) { + StringBuilder sb = new StringBuilder(); int count = 0; for (int i = begin; i <= end; i++) { - sb.append(list[i] + "\t"); - if (++count == 10) { + if (count != 0 && count % 10 == 0) { sb.append("\n"); - count = 0; } + sb.append("\t" + arr[i]); + count++; } return sb.toString(); @@ -38,11 +99,18 @@ public static String getArrayString(T[] list, int begin, int end) { /** * 随机指定范围内N个不重复的Int数组。 - *

在初始化的无重复待选数组中随机产生一个数放入结果中,

- *

将待选数组被随机到的数,用待选数组(len-1)下标对应的数替换,

- *

然后从len-2里随机产生下一个随机数,如此类推

- * @param min 指定范围最小值 - * @param max 指定范围最大值 + *

+ * 在初始化的无重复待选数组中随机产生一个数放入结果中, + *

+ *

+ * 将待选数组被随机到的数,用待选数组(len-1)下标对应的数替换, + *

+ *

+ * 然后从len-2里随机产生下一个随机数,如此类推 + *

+ * + * @param min 指定范围最小值 + * @param max 指定范围最大值 * @param length 随机数个数 * @return int[] 随机数结果集 */ @@ -75,8 +143,9 @@ public static int[] randomNoRepeatIntArray(int min, int max, int length) { /** * 随机指定范围内N个重复的Int数组。 - * @param min 指定范围最小值 - * @param max 指定范围最大值 + * + * @param min 指定范围最小值 + * @param max 指定范围最大值 * @param length 随机数个数 * @return 随机数结果集 */ @@ -96,11 +165,18 @@ public static int[] randomRepeatIntArray(int min, int max, int length) { /** * 随机指定范围内N个不重复的Integer数组。 - *

在初始化的无重复待选数组中随机产生一个数放入结果中,

- *

将待选数组被随机到的数,用待选数组(len-1)下标对应的数替换,

- *

然后从len-2里随机产生下一个随机数,如此类推

- * @param min 指定范围最小值 - * @param max 指定范围最大值 + *

+ * 在初始化的无重复待选数组中随机产生一个数放入结果中, + *

+ *

+ * 将待选数组被随机到的数,用待选数组(len-1)下标对应的数替换, + *

+ *

+ * 然后从len-2里随机产生下一个随机数,如此类推 + *

+ * + * @param min 指定范围最小值 + * @param max 指定范围最大值 * @param length 随机数个数 * @return int[] 随机数结果集 */ @@ -133,8 +209,9 @@ public static Integer[] randomNoRepeatIntegerArray(int min, int max, int length) /** * 随机指定范围内N个重复的Integer数组。 - * @param min 指定范围最小值 - * @param max 指定范围最大值 + * + * @param min 指定范围最小值 + * @param max 指定范围最大值 * @param length 随机数个数 * @return 随机数结果集 */ @@ -151,4 +228,5 @@ public static Integer[] randomRepeatIntegerArray(int min, int max, int length) { } return result; } + } diff --git a/codes/algorithm/src/main/resources/logback.xml b/codes/algorithm/src/main/resources/logback.xml index 511ee21..3a5812a 100644 --- a/codes/algorithm/src/main/resources/logback.xml +++ b/codes/algorithm/src/main/resources/logback.xml @@ -3,7 +3,7 @@ - + @@ -31,13 +31,13 @@ - - - + + + - + diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/IteratorTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/IteratorTest.java similarity index 89% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/IteratorTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/IteratorTest.java index e430611..6aa8bcb 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/IteratorTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/IteratorTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; import java.util.Iterator; @@ -14,4 +14,5 @@ public static > boolean testIterator(Iterator iter) { } return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/JavaCollectionTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/JavaCollectionTest.java similarity index 94% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/JavaCollectionTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/JavaCollectionTest.java index e23225f..ccf9bb6 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/JavaCollectionTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/JavaCollectionTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; import java.util.Collection; @@ -70,8 +70,8 @@ private static > boolean addAndRemoveInOrder(Collection< } } - boolean contains = collection.contains((T) invalid); - boolean removed = collection.remove((T) invalid); + boolean contains = collection.contains(invalid); + boolean removed = collection.remove(invalid); if (contains || removed) { System.err.println(name + " invalidity check. contains=" + contains + " removed=" + removed); Utils.handleError(_invalid, collection); @@ -110,24 +110,25 @@ private static > boolean addAndRemoveInOrder(Collection< return true; } - private static > boolean addInReverseOrderAndRemoveInOrder(Collection collection, + public static > boolean addInOrderRemoveInReverseOrder(Collection collection, Class type, String name, Integer[] data, Integer _invalid) { T invalid = Utils.parseT(_invalid, type); - // Add in reverse (from index length-1 to zero) order and then remove in order (from index zero to length) - for (int i = data.length - 1; i >= 0; i--) { + // Add in order (from index zero to length) and then remove in reverse (from index + // length-1 to zero) order + for (int i = 0; i < data.length; i++) { Integer value = data[i]; T item = Utils.parseT(value, type); boolean added = collection.add(item); if (!added) { - System.err.println(name + " addInReverseOrderAndRemoveInOrder add failed."); + System.err.println(name + " addInOrderRemoveInReverseOrder add failed."); Utils.handleError(data, collection); return false; } } - boolean contains = collection.contains((T) invalid); - boolean removed = collection.remove((T) invalid); + boolean contains = collection.contains(invalid); + boolean removed = collection.remove(invalid); if (contains || removed) { System.err.println(name + " invalidity check. contains=" + contains + " removed=" + removed); Utils.handleError(_invalid, collection); @@ -135,7 +136,7 @@ private static > boolean addInReverseOrderAndRemoveInOrd } if (!IteratorTest.testIterator(collection.iterator())) { - System.err.println(name + " addInReverseOrderAndRemoveInOrder iterator failed."); + System.err.println(name + " addInOrderRemoveInReverseOrder iterator failed."); Utils.handleError(data, collection); return false; } @@ -145,31 +146,37 @@ private static > boolean addInReverseOrderAndRemoveInOrd T item = Utils.parseT(value, type); contains = collection.contains(item); if (!contains) { - System.err.println(name + " addInReverseOrderAndRemoveInOrder contains failed."); + System.err.println(name + " addInOrderRemoveInReverseOrder contains failed."); Utils.handleError(data, collection); return false; } } - for (int i = 0; i < data.length; i++) { + for (int i = data.length - 1; i >= 0; i--) { Integer value = data[i]; T item = Utils.parseT(value, type); removed = collection.remove(item); if (!removed) { - System.err.println(name + " addInReverseOrderAndRemoveInOrder remove failed."); + System.err.println(name + " addInOrderRemoveInReverseOrder remove failed."); Utils.handleError(data, collection); return false; } } if (!collection.isEmpty()) { - System.err.println(name + " addInReverseOrderAndRemoveInOrder isEmpty() failed."); + System.err.println(name + " addInOrderRemoveInReverseOrder isEmpty() failed."); Utils.handleError(data, collection); return false; } - if (collection.size() != 0) { - System.err.println(name + " addInReverseOrderAndRemoveInOrder size() failed."); + System.err.println(name + " addInOrderRemoveInReverseOrder size() failed."); + Utils.handleError(data, collection); + return false; + } + + if (collection instanceof java.util.List && (!ListIteratorTest + .testListIterator(((java.util.List) collection).listIterator(), type, data, data.length))) { + System.err.println(name + " addInOrderRemoveInReverseOrder list iterator failed."); Utils.handleError(data, collection); return false; } @@ -177,24 +184,25 @@ private static > boolean addInReverseOrderAndRemoveInOrd return true; } - public static > boolean addInOrderRemoveInReverseOrder(Collection collection, + private static > boolean addInReverseOrderAndRemoveInOrder(Collection collection, Class type, String name, Integer[] data, Integer _invalid) { T invalid = Utils.parseT(_invalid, type); - // Add in order (from index zero to length) and then remove in reverse (from index length-1 to zero) order - for (int i = 0; i < data.length; i++) { + // Add in reverse (from index length-1 to zero) order and then remove in order + // (from index zero to length) + for (int i = data.length - 1; i >= 0; i--) { Integer value = data[i]; T item = Utils.parseT(value, type); boolean added = collection.add(item); if (!added) { - System.err.println(name + " addInOrderRemoveInReverseOrder add failed."); + System.err.println(name + " addInReverseOrderAndRemoveInOrder add failed."); Utils.handleError(data, collection); return false; } } - boolean contains = collection.contains((T) invalid); - boolean removed = collection.remove((T) invalid); + boolean contains = collection.contains(invalid); + boolean removed = collection.remove(invalid); if (contains || removed) { System.err.println(name + " invalidity check. contains=" + contains + " removed=" + removed); Utils.handleError(_invalid, collection); @@ -202,7 +210,7 @@ public static > boolean addInOrderRemoveInReverseOrder(C } if (!IteratorTest.testIterator(collection.iterator())) { - System.err.println(name + " addInOrderRemoveInReverseOrder iterator failed."); + System.err.println(name + " addInReverseOrderAndRemoveInOrder iterator failed."); Utils.handleError(data, collection); return false; } @@ -212,41 +220,36 @@ public static > boolean addInOrderRemoveInReverseOrder(C T item = Utils.parseT(value, type); contains = collection.contains(item); if (!contains) { - System.err.println(name + " addInOrderRemoveInReverseOrder contains failed."); + System.err.println(name + " addInReverseOrderAndRemoveInOrder contains failed."); Utils.handleError(data, collection); return false; } } - for (int i = data.length - 1; i >= 0; i--) { + for (int i = 0; i < data.length; i++) { Integer value = data[i]; T item = Utils.parseT(value, type); removed = collection.remove(item); if (!removed) { - System.err.println(name + " addInOrderRemoveInReverseOrder remove failed."); + System.err.println(name + " addInReverseOrderAndRemoveInOrder remove failed."); Utils.handleError(data, collection); return false; } } if (!collection.isEmpty()) { - System.err.println(name + " addInOrderRemoveInReverseOrder isEmpty() failed."); - Utils.handleError(data, collection); - return false; - } - if (collection.size() != 0) { - System.err.println(name + " addInOrderRemoveInReverseOrder size() failed."); + System.err.println(name + " addInReverseOrderAndRemoveInOrder isEmpty() failed."); Utils.handleError(data, collection); return false; } - if (collection instanceof java.util.List && (!ListIteratorTest - .testListIterator(((java.util.List) collection).listIterator(), type, data, data.length))) { - System.err.println(name + " addInOrderRemoveInReverseOrder list iterator failed."); + if (collection.size() != 0) { + System.err.println(name + " addInReverseOrderAndRemoveInOrder size() failed."); Utils.handleError(data, collection); return false; } return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/JavaMapTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/JavaMapTest.java similarity index 97% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/JavaMapTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/JavaMapTest.java index 974ae87..db018a3 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/JavaMapTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/JavaMapTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; @SuppressWarnings("unchecked") public class JavaMapTest { @@ -79,11 +79,9 @@ private static > boolean addInOrderAndRemoveInOrde Utils.handleError(data, map); return false; } - } - if (!testMapEntrySet(map, keyType, data)) - return false; + if (!testMapEntrySet(map, keyType, data)) { return false; } if (!map.isEmpty()) { System.err.println(name + " isEmpty() failed."); @@ -193,8 +191,7 @@ private static > boolean addInOrderAndRemoveInReve } } - if (!testMapEntrySet(map, keyType, data)) - return false; + if (!testMapEntrySet(map, keyType, data)) { return false; } if (!map.isEmpty()) { System.err.println(name + " sorted isEmpty() failed."); @@ -211,7 +208,7 @@ private static > boolean addInOrderAndRemoveInReve private static > boolean testMapEntrySet(java.util.Map map, Class keyType, Integer[] data) { - { // Test keys + { // Test keys for (int i = 0; i < data.length; i++) { Integer item = data[i]; K k = null; @@ -255,7 +252,7 @@ private static > boolean testMapEntrySet(java.util } } - { // Test values + { // Test values for (int i = 0; i < data.length; i++) { Integer item = data[i]; K k = null; @@ -306,4 +303,5 @@ private static > boolean testMapEntrySet(java.util } return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/ListIteratorTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/ListIteratorTest.java similarity index 95% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/ListIteratorTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/ListIteratorTest.java index f82a128..4da00d1 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/ListIteratorTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/ListIteratorTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; import java.util.ListIterator; import java.util.NoSuchElementException; @@ -24,8 +24,7 @@ public static > boolean testListIterator(ListIterator T item = Utils.parseT(value, type); iter.add(item); } - while (iter.hasPrevious()) - iter.previous(); + while (iter.hasPrevious()) { iter.previous(); } int i = 0; while (iter.hasNext()) { @@ -53,7 +52,7 @@ public static > boolean testListIterator(ListIterator return false; } - //This should be list.size + // This should be list.size iter.nextIndex(); int listSize = iter.nextIndex(); if (listSize != size) { @@ -103,4 +102,5 @@ public static > boolean testListIterator(ListIterator return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/ListTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/ListTest.java similarity index 99% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/ListTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/ListTest.java index f7b54c5..93930fc 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/ListTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/ListTest.java @@ -1,5 +1,4 @@ -package io.github.dunwu.ds.common; - +package io.github.dunwu.algorithm.common; public class ListTest { @@ -111,4 +110,5 @@ public static > boolean testList(IList list, String n return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/MapTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/MapTest.java similarity index 99% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/MapTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/MapTest.java index 56636b8..10b3e59 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/MapTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/MapTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; public class MapTest { @@ -162,4 +162,5 @@ public static > boolean testMap(IMap map, Cl return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/QueueTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/QueueTest.java similarity index 99% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/QueueTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/QueueTest.java index b219ac2..17301db 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/QueueTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/QueueTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; public class QueueTest { @@ -146,4 +146,5 @@ public static > boolean testQueue(IQueue queue, Strin return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/SetTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/SetTest.java similarity index 99% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/SetTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/SetTest.java index 590e6c3..f989017 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/SetTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/SetTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; public class SetTest { @@ -110,4 +110,5 @@ public static > boolean testSet(ISet set, String name return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/StackTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/StackTest.java similarity index 99% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/StackTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/StackTest.java index c63ae7f..3601c5d 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/StackTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/StackTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; public class StackTest { @@ -125,4 +125,5 @@ public static > boolean testStack(IStack stack, Strin return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/SuffixTreeTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/SuffixTreeTest.java similarity index 96% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/SuffixTreeTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/SuffixTreeTest.java index d015f6a..0eb4ebb 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/SuffixTreeTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/SuffixTreeTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; public class SuffixTreeTest { @@ -6,6 +6,7 @@ public class SuffixTreeTest { * In computer science, a suffix tree (also called PAT tree or, in an earlier form, position tree) is a compressed * trie containing all the suffixes of the given text as their keys and positions in the text as their values. * Suffix trees allow particularly fast implementations of many important string operations. + * * @param tree Suffix tree to test. * @param test String to use in testing the suffix tree. * @return True if the suffix tree passes it's invariants tests. @@ -36,4 +37,5 @@ public static boolean suffixTreeTest(ISuffixTree tree, String test) { return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/TreeTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/TreeTest.java similarity index 99% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/TreeTest.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/TreeTest.java index 9bac2bd..4c6de6e 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/TreeTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/TreeTest.java @@ -1,4 +1,4 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; public class TreeTest { @@ -118,4 +118,5 @@ public static > boolean testTree(ITree tree, Class return true; } + } diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/Utils.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/Utils.java similarity index 93% rename from codes/data-structure/src/test/java/io/github/dunwu/ds/common/Utils.java rename to codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/Utils.java index 50f9e09..67b6d28 100644 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/common/Utils.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/common/Utils.java @@ -1,10 +1,12 @@ -package io.github.dunwu.ds.common; +package io.github.dunwu.algorithm.common; import java.util.Arrays; import java.util.Random; public class Utils { + private static final Random RANDOM = new Random(); + public static final T parseT(final Integer value, final Class type) { T returnValue = null; @@ -40,8 +42,6 @@ public static void handleError(Object[] data, Object obj) { throw new RuntimeException("Error in test."); } - private static final Random RANDOM = new Random(); - public static TestData testData(int... integers) { TestData data = new TestData(integers.length); @@ -52,8 +52,7 @@ public static TestData testData(int... integers) { for (int i = 0; i < integers.length; i++) { Integer j = integers[i]; data.unsorted[i] = j; - if (i != integers.length - 1) - builder.append(j).append(','); + if (i != integers.length - 1) { builder.append(j).append(','); } } set.clear(); set = null; @@ -87,8 +86,7 @@ public static TestData generateTestData(int data_size) { } } data.unsorted[i] = j; - if (i != data_size - 1) - builder.append(j).append(','); + if (i != data_size - 1) { builder.append(j).append(','); } } set.clear(); set = null; @@ -104,15 +102,14 @@ public static TestData generateTestData(int data_size) { public static class TestData { public int random_size = 0; + public Integer invalid = 0; + public Integer[] unsorted = null; + public Integer[] sorted = null; - public String string = null; - public TestData(int size) { - this.random_size = 1000 * size; - this.invalid = random_size + 10; - } + public String string = null; public TestData(Integer[] _unsorted) { this(_unsorted.length); @@ -122,16 +119,22 @@ public TestData(Integer[] _unsorted) { setString(unsorted); } + public TestData(int size) { + this.random_size = 1000 * size; + this.invalid = random_size + 10; + } + private static final String setString(Integer[] _unsorted) { StringBuilder builder = new StringBuilder(); builder.append("Array="); for (int i = 0; i < _unsorted.length; i++) { Integer d = _unsorted[i]; - if (i != _unsorted.length - 1) - builder.append(d).append(','); + if (i != _unsorted.length - 1) { builder.append(d).append(','); } } builder.append('\n'); return builder.toString(); } + } + } diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/DoubleLinkListTests.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/DoubleLinkListTests.java new file mode 100644 index 0000000..89c3766 --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/DoubleLinkListTests.java @@ -0,0 +1,84 @@ +package io.github.dunwu.algorithm.list; + +import io.github.dunwu.algorithm.linkedlist.demo.DoublyLinkedList; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * @author Zhang Peng + * @since 2020-01-26 + */ +public class DoubleLinkListTests { + + @Test + public void addTest() { + DoublyLinkedList list = new DoublyLinkedList<>(); + list.addLast(2); + list.addLast(3); + list.addFirst(1); + List result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, result.toArray()); + } + + @Test + public void removeFirstTest() { + DoublyLinkedList list = new DoublyLinkedList<>(); + list.addLast(1); + list.addLast(1); + list.remove(new Integer(1)); + List result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1 }, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.remove(new Integer(1)); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 2, 3 }, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.remove(new Integer(3)); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2 }, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.remove(new Integer(4)); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2 }, result.toArray()); + } + + @Test + public void removeAllTest() { + DoublyLinkedList list = new DoublyLinkedList<>(); + list.addLast(1); + list.addLast(1); + list.addLast(1); + list.removeAll(1); + List result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] {}, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.removeAll(4); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, result.toArray()); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/SingleLinkListTests.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/SingleLinkListTests.java new file mode 100644 index 0000000..15020fd --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/list/SingleLinkListTests.java @@ -0,0 +1,84 @@ +package io.github.dunwu.algorithm.list; + +import io.github.dunwu.algorithm.linkedlist.demo.SinglyLinkedList; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * @author Zhang Peng + * @since 2020-01-26 + */ +public class SingleLinkListTests { + + @Test + public void addTest() { + SinglyLinkedList list = new SinglyLinkedList<>(); + list.addLast(2); + list.addLast(3); + list.addFirst(1); + List result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, result.toArray()); + } + + @Test + public void removeFirstTest() { + SinglyLinkedList list = new SinglyLinkedList<>(); + list.addLast(1); + list.addLast(1); + list.remove(new Integer(1)); + List result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1 }, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.remove(new Integer(1)); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 2, 3 }, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.remove(new Integer(3)); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2 }, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.remove(new Integer(4)); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2 }, result.toArray()); + } + + @Test + public void removeAllTest() { + SinglyLinkedList list = new SinglyLinkedList<>(); + list.addLast(1); + list.addLast(1); + list.addLast(1); + list.removeAll(1); + List result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] {}, result.toArray()); + + list.clear(); + list.addLast(1); + list.addLast(2); + list.addLast(3); + list.removeAll(4); + result = list.toList(); + System.out.println(result); + Assertions.assertArrayEquals(new Integer[] { 1, 2, 3 }, result.toArray()); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/search/SearchStrategyTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/search/SearchStrategyTest.java index 2460d26..7aa8ca4 100644 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/search/SearchStrategyTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/search/SearchStrategyTest.java @@ -1,49 +1,51 @@ -package io.github.dunwu.ds.search; +package io.github.dunwu.algorithm.search; + +import io.github.dunwu.algorithm.search.strategy.BinarySearch; +import io.github.dunwu.algorithm.search.strategy.OrderSearch; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import io.github.dunwu.ds.search.strategy.BinarySearch; -import io.github.dunwu.ds.search.strategy.OrderSearch; -import io.github.dunwu.ds.util.ArrayUtil; import java.util.Random; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runners.MethodSorters; + +// import io.github.dunwu.algorithm.util.ArrayUtil; /** - * 排序算法单元测试 - * 如果需要打印每趟排序的结果,可以修改 logback.xml 中 + * 排序算法单元测试 如果需要打印每趟排序的结果,可以修改 logback.xml 中 * 的 level 级别,改为 DEBUG, * 日志就会打印 debug 信息。 * * @author Zhang Peng */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class SearchStrategyTest { /** * 随机样本一 */ private static Integer[] origin01; + private static int expected01; /** * 随机样本二 */ private static Integer[] origin02; + private static int expected02; /** * 随机样本三 */ private static Integer[] origin03; + private static int expected03; /** * 生成随机数组样本,并调用 JDK api 生成期望的有序数组 */ - @BeforeClass + @BeforeAll public static void beforeClass() { Random random = new Random(); @@ -63,7 +65,7 @@ public static void beforeClass() { /** * 每次执行 @Test 前都使用生成的随机样本初始化实际用于排序的数组 */ - @Before + @BeforeEach public void before() { } @@ -73,23 +75,24 @@ public void testOrderSearch() { executeSearch(strategy); } - @Test - public void testBinarySearch() { - SearchStrategy strategy = new SearchStrategy(new BinarySearch()); - executeSearch(strategy); - } - /** * 注入 BinarySearch,执行对三个样本的排序测试 */ private void executeSearch(SearchStrategy strategy) { int target01 = strategy.find(origin01, origin01[expected01]); - Assert.assertEquals(expected01, target01); + Assertions.assertEquals(expected01, target01); int target02 = strategy.find(origin02, origin02[expected02]); - Assert.assertEquals(expected02, target02); + Assertions.assertEquals(expected02, target02); int target03 = strategy.find(origin03, origin03[expected03]); - Assert.assertEquals(expected03, target03); + Assertions.assertEquals(expected03, target03); } + + @Test + public void testBinarySearch() { + SearchStrategy strategy = new SearchStrategy(new BinarySearch()); + executeSearch(strategy); + } + } diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java index 2d1ae7c..2783f81 100644 --- a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java @@ -1,83 +1,92 @@ -package io.github.dunwu.ds.sort; - -import io.github.dunwu.ds.sort.strategy.BubbleSort; -import io.github.dunwu.ds.sort.strategy.BubbleSort2; -import io.github.dunwu.ds.sort.strategy.HeapSort; -import io.github.dunwu.ds.sort.strategy.InsertSort; -import io.github.dunwu.ds.sort.strategy.MergeSort; -import io.github.dunwu.ds.sort.strategy.QuickSort; -import io.github.dunwu.ds.sort.strategy.SelectionSort; -import io.github.dunwu.ds.sort.strategy.ShellSort; -import io.github.dunwu.ds.util.ArrayUtil; +package io.github.dunwu.algorithm.sort; + +import io.github.dunwu.algorithm.sort.strategy.BubbleSort; +import io.github.dunwu.algorithm.sort.strategy.BubbleSort2; +import io.github.dunwu.algorithm.sort.strategy.HeapSort; +import io.github.dunwu.algorithm.sort.strategy.InsertSort; +import io.github.dunwu.algorithm.sort.strategy.MergeSort; +import io.github.dunwu.algorithm.sort.strategy.QuickSort; +import io.github.dunwu.algorithm.sort.strategy.SelectionSort; +import io.github.dunwu.algorithm.sort.strategy.ShellSort; +import io.github.dunwu.algorithm.util.ArrayUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import java.util.Arrays; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runners.MethodSorters; /** - * 排序算法单元测试 - * 如果需要打印每趟排序的结果,可以修改 logback.xml 中 + * 排序算法单元测试 如果需要打印每趟排序的结果,可以修改 logback.xml 中 * 的 level 级别,改为 DEBUG, * 日志就会打印 debug 信息。 * * @author Zhang Peng */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class SortStrategyTest { /** * 随机样本一 */ - private static Integer[] origin01; - private static Integer[] target01; - private static Integer[] expected01; + private static Integer[] s1; + private static Integer[] t1; + private static Integer[] e1; /** * 随机样本二 */ - private static Integer[] origin02; - private static Integer[] target02; - private static Integer[] expected02; + private static Integer[] s2; + private static Integer[] t2; + private static Integer[] e2; /** * 随机样本三 */ - private static Integer[] origin03; - private static Integer[] target03; - private static Integer[] expected03; + private static Integer[] s3; + private static Integer[] t3; + private static Integer[] e3; /** * 生成随机数组样本,并调用 JDK api 生成期望的有序数组 */ - @BeforeClass + @BeforeAll public static void beforeClass() { // 在 [0, 100] 间生成长度为 10 的存在重复的随机数组 - origin01 = ArrayUtil.randomRepeatIntegerArray(0, 10, 9); - expected01 = Arrays.copyOf(origin01, origin01.length); - Arrays.sort(expected01); + s1 = ArrayUtil.randomRepeatIntegerArray(0, 10, 5); + e1 = Arrays.copyOf(s1, s1.length); + Arrays.sort(e1); // 在 [0, 100] 间生成长度为 17 的不重复的随机数组 - origin02 = ArrayUtil.randomNoRepeatIntegerArray(0, 100, 17); - expected02 = Arrays.copyOf(origin02, origin02.length); - Arrays.sort(expected02); + s2 = ArrayUtil.randomNoRepeatIntegerArray(0, 100, 10); + e2 = Arrays.copyOf(s2, s2.length); + Arrays.sort(e2); // 在 [0, 100] 间生成长度为 100 的不重复的随机数组 - origin03 = ArrayUtil.randomNoRepeatIntegerArray(0, 100, 100); - expected03 = Arrays.copyOf(origin03, origin03.length); - Arrays.sort(expected03); + s3 = ArrayUtil.randomNoRepeatIntegerArray(0, 100, 30); + e3 = Arrays.copyOf(s3, s3.length); + Arrays.sort(e3); + } + + /** + * 注入 SortStrategy,执行对三个样本的排序测试 + */ + private void executeSort(SortStrategy strategy) { + strategy.sort(t1); + Assertions.assertArrayEquals(e1, t1); + strategy.sort(t2); + Assertions.assertArrayEquals(e2, t2); + strategy.sort(t3); + Assertions.assertArrayEquals(e3, t3); } /** * 每次执行 @Test 前都使用生成的随机样本初始化实际用于排序的数组 */ - @Before + @BeforeEach public void before() { - target01 = Arrays.copyOf(origin01, origin01.length); - target02 = Arrays.copyOf(origin02, origin02.length); - target03 = Arrays.copyOf(origin03, origin03.length); + t1 = Arrays.copyOf(s1, s1.length); + t2 = Arrays.copyOf(s2, s2.length); + t3 = Arrays.copyOf(s3, s3.length); } @Test @@ -128,15 +137,4 @@ public void testMergeSort() { executeSort(strategy); } - /** - * 注入 SortStrategy,执行对三个样本的排序测试 - */ - private void executeSort(SortStrategy strategy) { - strategy.sort(target01); - Assert.assertArrayEquals(expected01, target01); - strategy.sort(target02); - Assert.assertArrayEquals(expected02, target02); - strategy.sort(target03); - Assert.assertArrayEquals(expected03, target03); - } } diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/AddBinaryTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/AddBinaryTest.java new file mode 100644 index 0000000..993ada9 --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/AddBinaryTest.java @@ -0,0 +1,18 @@ +package io.github.dunwu.algorithm.str; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Zhang Peng + * @since 2018-11-05 + */ +public class AddBinaryTest { + + @Test + public void test() { + Assertions.assertEquals("100", AddBinary.addBinary("11", "1")); + Assertions.assertEquals("10101", AddBinary.addBinary("1010", "1011")); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ImplementStrstrTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ImplementStrstrTest.java new file mode 100644 index 0000000..ff65cdf --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ImplementStrstrTest.java @@ -0,0 +1,23 @@ +package io.github.dunwu.algorithm.str; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Zhang Peng + * @since 2018-11-05 + */ +public class ImplementStrstrTest { + + @Test + public void test() { + Assertions.assertEquals(0, ImplementStrstr.strStr("", "")); + Assertions.assertEquals(-1, ImplementStrstr.strStr("aaa", "aaaa")); + Assertions.assertEquals(0, ImplementStrstr.strStr("aaa", "")); + Assertions.assertEquals(2, ImplementStrstr.strStr("hello", "ll")); + Assertions.assertEquals(-1, ImplementStrstr.strStr("aaaaa", "bba")); + Assertions.assertEquals(1, ImplementStrstr.strStr("mississippi", "issi")); + Assertions.assertEquals(9, ImplementStrstr.strStr("mississippi", "pi")); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/LongestCommonPrefixTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/LongestCommonPrefixTest.java new file mode 100644 index 0000000..08d8729 --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/LongestCommonPrefixTest.java @@ -0,0 +1,21 @@ +package io.github.dunwu.algorithm.str; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Zhang Peng + * @since 2018-11-05 + */ +public class LongestCommonPrefixTest { + + @Test + public void test() { + String[] strs1 = { "flower", "flow", "flight" }; + String[] strs2 = { "dog", "racecar", "car" }; + + Assertions.assertEquals("fl", LongestCommonPrefix.longestCommonPrefix(strs1)); + Assertions.assertEquals("", LongestCommonPrefix.longestCommonPrefix(strs2)); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ReverseStringTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ReverseStringTest.java new file mode 100644 index 0000000..70da08d --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ReverseStringTest.java @@ -0,0 +1,19 @@ +package io.github.dunwu.algorithm.str; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Zhang Peng + * @since 2018-11-05 + */ +public class ReverseStringTest { + + @Test + public void test() { + Assertions.assertEquals("olleh", ReverseString.reverseString("hello")); + Assertions.assertEquals("amanaP :lanac a ,nalp a ,nam A", + ReverseString.reverseString("A man, a plan, a canal: Panama")); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ReverseWordsInAString3Test.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ReverseWordsInAString3Test.java new file mode 100644 index 0000000..492b368 --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ReverseWordsInAString3Test.java @@ -0,0 +1,18 @@ +package io.github.dunwu.algorithm.str; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Zhang Peng + * @since 2018-11-05 + */ +public class ReverseWordsInAString3Test { + + @Test + public void test() { + Assertions.assertEquals("s'teL ekat edoCteeL tsetnoc", + ReverseWordsInAString3.reverseWords("Let's take LeetCode contest")); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ReverseWordsInAStringTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ReverseWordsInAStringTest.java new file mode 100644 index 0000000..212d0dd --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/str/ReverseWordsInAStringTest.java @@ -0,0 +1,19 @@ +package io.github.dunwu.algorithm.str; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Zhang Peng + * @since 2018-11-05 + */ +public class ReverseWordsInAStringTest { + + @Test + public void test() { + Assertions.assertEquals("blue is sky the", ReverseWordsInAString.reverseWords("the sky is blue")); + Assertions.assertEquals(" ", ReverseWordsInAString.reverseWords(" ")); + Assertions.assertEquals("1", ReverseWordsInAString.reverseWords("1 ")); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/string/StringAlgorithmTest.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/string/StringAlgorithmTest.java new file mode 100644 index 0000000..7aa1f30 --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/string/StringAlgorithmTest.java @@ -0,0 +1,97 @@ +package io.github.dunwu.algorithm.string; + +import io.github.dunwu.algorithm.str.StringAlgorithm; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Zhang Peng + * @since 2020-01-18 + */ +public class StringAlgorithmTest { + + @Test + public void lengthOfLongestSubstring() { + int len = StringAlgorithm.lengthOfLongestSubstring("abcabcbb"); + Assertions.assertEquals(3, len); + + len = StringAlgorithm.lengthOfLongestSubstring("bbbbb"); + Assertions.assertEquals(1, len); + + len = StringAlgorithm.lengthOfLongestSubstring("pwwkew"); + Assertions.assertEquals(3, len); + } + + @Test + public void longestCommonPrefix() { + String str = StringAlgorithm.longestCommonPrefix(new String[] { "flower", "flow", "flight" }); + Assertions.assertEquals("fl", str); + + str = StringAlgorithm.longestCommonPrefix(new String[] { "dog", "racecar", "car" }); + Assertions.assertEquals("", str); + } + + @Test + public void checkInclusion() { + boolean result = StringAlgorithm.checkInclusion("ab", "eidbaooo"); + Assertions.assertEquals(true, result); + + result = StringAlgorithm.checkInclusion("ab", "eidboaoo"); + Assertions.assertEquals(false, result); + } + + @Test + public void multiply() { + String result = StringAlgorithm.multiply("2", "3"); + Assertions.assertEquals("6", result); + + result = StringAlgorithm.multiply("333", "2"); + Assertions.assertEquals("666", result); + + result = StringAlgorithm.multiply("123", "456"); + Assertions.assertEquals("56088", result); + + result = StringAlgorithm.multiply("123456789", "987654321"); + Assertions.assertEquals("121932631112635269", result); + + result = StringAlgorithm.multiply("498828660196", "840477629533"); + Assertions.assertEquals("419254329864656431168468", result); + } + + @Test + public void add() { + String result = StringAlgorithm.add("100000000000000000000", "8888"); + Assertions.assertEquals("100000000000000008888", result); + + result = StringAlgorithm.add("1368", "9120"); + Assertions.assertEquals("10488", result); + } + + @Test + public void reverseWords() { + String result = StringAlgorithm.reverseWords("the sky is blue"); + Assertions.assertEquals("blue is sky the", result); + + result = StringAlgorithm.reverseWords(" hello world! "); + Assertions.assertEquals("world! hello", result); + + result = StringAlgorithm.reverseWords("a good example"); + Assertions.assertEquals("example good a", result); + } + + @Test + public void simplifyPath() { + String result = StringAlgorithm.simplifyPath("/home/"); + Assertions.assertEquals("/home", result); + + result = StringAlgorithm.simplifyPath("/../"); + Assertions.assertEquals("/", result); + + result = StringAlgorithm.simplifyPath("/home//foo/"); + Assertions.assertEquals("/home/foo", result); + + result = StringAlgorithm.simplifyPath("/a/./b/../../c/"); + Assertions.assertEquals("/c", result); + } + +} diff --git a/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeTests.java b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeTests.java new file mode 100644 index 0000000..f53c916 --- /dev/null +++ b/codes/algorithm/src/test/java/io/github/dunwu/algorithm/tree/BTreeTests.java @@ -0,0 +1,66 @@ +package io.github.dunwu.algorithm.tree; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author Zhang Peng + * @since 2020-01-28 + */ +public class BTreeTests { + + @Test + @DisplayName("二叉树的最大深度") + public void maxDepthTest() { + BTree tree = BTree.build(1, 2, 3, 4, 5); + Assertions.assertEquals(3, tree.maxDepth()); + } + + @Test + @DisplayName("二叉树的最小深度") + public void minDepthTest() { + BTree tree = BTree.build(3, 9, 20, null, null, 15, 7); + Assertions.assertEquals(2, tree.minDepth()); + + tree = BTree.build(1, 2); + Assertions.assertEquals(2, tree.minDepth()); + } + + @Test + @DisplayName("判断两颗二叉树是否完全一致") + public void isEqualsTest() { + BTree tree1 = BTree.build(1, 2, 3); + BTree tree2 = BTree.build(1, 2, 3); + Assertions.assertTrue(BTree.isEquals(tree1, tree2)); + + tree1 = BTree.build(1, 2, 1); + tree2 = BTree.build(1, 1, 2); + Assertions.assertFalse(BTree.isEquals(tree1, tree2)); + } + + @Test + @DisplayName("广度优先搜索(BFS)") + public void levelOrderBottomTest() { + BTree tree = BTree.build(3, 9, 20, null, null, 15, 7); + List> lists = new ArrayList<>(); + lists.add(Collections.singletonList(3)); + lists.add(Arrays.asList(9, 20)); + lists.add(Arrays.asList(15, 7)); + Assertions.assertIterableEquals(lists, tree.levelOrderLists()); + } + + @Test + @DisplayName("判断两颗二叉树的叶子节点是否相似") + public void isLeafSimilarTest() { + BTree tree1 = BTree.build(3, 5, 1, 6, 2, 9, 8, null, null, 7, 4); + BTree tree2 = BTree.build(3, 5, 1, 6, 7, 4, 2, null, null, null, null, null, null, 9, 8); + Assertions.assertTrue(BTree.isLeafSimilar(tree1, tree2)); + } + +} diff --git a/codes/data-structure/pom.xml b/codes/data-structure/pom.xml deleted file mode 100644 index 7e8bf3b..0000000 --- a/codes/data-structure/pom.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - 4.0.0 - - io.github.dunwu - data-structure-demos - 1.0.1 - jar - Data Structure Demos - 数据结构 - - - UTF-8 - 1.8 - ${java.version} - ${java.version} - - - - - ch.qos.logback - logback-classic - 1.1.3 - - - junit - junit - 4.12 - test - - - diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/ArrayPartition.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/ArrayPartition.java deleted file mode 100644 index d57ff71..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/ArrayPartition.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【数组拆分 I】 -// -// 给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大。 -// -// 示例 1: -// -// 输入: [1,4,3,2] -// -// 输出: 4 -// 解释: n 等于 2, 最大总和为 4 = min(1, 2) + min(3, 4). -// 提示: -// -// n 是正整数,范围在 [1, 10000]. -// 数组中的元素范围在 [-10000, 10000]. - - -import java.util.Arrays; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class ArrayPartition { - public static int arrayPairSum(int[] nums) { - Arrays.sort(nums); - int result = 0; - for (int i = 0; i < nums.length; i += 2) { - result += nums[i]; - } - return result; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/DiagonalTraverse.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/DiagonalTraverse.java deleted file mode 100644 index 042e4ce..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/DiagonalTraverse.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【对角线遍历】 -// -// 给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。 -// -// 示例: -// -// 输入: -// [ -// [ 1, 2, 3 ], -// [ 4, 5, 6 ], -// [ 7, 8, 9 ] -// ] -// -// 输出: [1,2,4,7,5,3,6,8,9] -// -// 说明: -// -// 给定矩阵中的元素总数不会超过 100000 。 - - -/** - * @author Zhang Peng - * @date 2018-11-04 - */ -public class DiagonalTraverse { - - public static int[] findDiagonalOrder(int[][] matrix) { - if (matrix.length == 0) { - return new int[0]; - } - - int x = 0, y = 0; - final int M = matrix.length; - final int N = matrix[0].length; - int[] arr = new int[M * N]; - for (int i = 0; i < arr.length; i++) { - arr[i] = matrix[x][y]; - if ((x + y) % 2 == 0) { - if (y == N - 1) { - x++; - } else if (x == 0) { - y++; - } else { - x--; - y++; - } - } else { - if (x == M - 1) { - y++; - } else if (y == 0) { - x++; - } else { - x++; - y--; - } - } - } - return arr; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/FindPivotIndex.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/FindPivotIndex.java deleted file mode 100644 index 6e73297..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/FindPivotIndex.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【寻找数组的中心索引】 -// -// 给定一个整数类型的数组 nums,请编写一个能够返回数组“中心索引”的方法。 -// -// 我们是这样定义数组中心索引的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。 -// -// 如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。 -// -// 示例 1: -// -// 输入: -// nums = [1, 7, 3, 6, 5, 6] -// 输出: 3 -// 解释: -// 索引3 (nums[3] = 6) 的左侧数之和(1 + 7 + 3 = 11),与右侧数之和(5 + 6 = 11)相等。 -// 同时, 3 也是第一个符合要求的中心索引。 -// 示例 2: -// -// 输入: -// nums = [1, 2, 3] -// 输出: -1 -// 解释: -// 数组中不存在满足此条件的中心索引。 -// 说明: -// -// nums 的长度范围为 [0, 10000]。 -// 任何一个 nums[i] 将会是一个范围在 [-1000, 1000]的整数。 - - -/** - * @author Zhang Peng - * @date 2018-11-04 - */ -public class FindPivotIndex { - public static int pivotIndex(int[] nums) { - int result = 0; - for (int i = 0; i < nums.length; i++) { - int sum1 = 0; - int sum2 = 0; - for (int a = 0; a < result; a++) { - sum1 += nums[a]; - } - - for (int b = result + 1; b < nums.length; b++) { - sum2 += nums[b]; - } - - if (sum1 == sum2) { - return result; - } - - result++; - } - return -1; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/LargestNumberAtLeastTwiceOfOthers.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/LargestNumberAtLeastTwiceOfOthers.java deleted file mode 100644 index 074d086..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/LargestNumberAtLeastTwiceOfOthers.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【至少是其他数字两倍的最大数】 -// -// 在一个给定的数组nums中,总是存在一个最大元素 。 -// -// 查找数组中的最大元素是否至少是数组中每个其他数字的两倍。 -// -// 如果是,则返回最大元素的索引,否则返回-1。 -// -// 示例 1: -// -// 输入: nums = [3, 6, 1, 0] -// 输出: 1 -// 解释: 6是最大的整数, 对于数组中的其他整数, -// 6大于数组中其他元素的两倍。6的索引是1, 所以我们返回1. -// -// -// 示例 2: -// -// 输入: nums = [1, 2, 3, 4] -// 输出: -1 -// 解释: 4没有超过3的两倍大, 所以我们返回 -1. -// -// -// 提示: -// -// nums 的长度范围在[1, 50]. -// 每个 nums[i] 的整数范围在 [0, 99]. - - -/** - * @author Zhang Peng - * @date 2018-11-04 - */ -public class LargestNumberAtLeastTwiceOfOthers { - public static int dominantIndex(int[] nums) { - int index = 0; - while (index < nums.length) { - boolean isMatch = true; - int max = nums[index]; - for (int i = 0; i < nums.length; i++) { - if (index != i && max < nums[i] * 2) { - isMatch = false; - break; - } - } - if (isMatch) { - return index; - } else { - index++; - } - } - return -1; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MaxConsecutiveOnes.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MaxConsecutiveOnes.java deleted file mode 100644 index b8f5265..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MaxConsecutiveOnes.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【最大连续1的个数】 -// -// 给定一个二进制数组, 计算其中最大连续1的个数。 -// -// 示例 1: -// -// 输入: [1,1,0,1,1,1] -// 输出: 3 -// 解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3. -// 注意: -// -// 输入的数组只包含 0 和1。 -// 输入数组的长度是正整数,且不超过 10,000。 - - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class MaxConsecutiveOnes { - public static int findMaxConsecutiveOnes(int[] nums) { - int max = 0; - int count = 0; - for (int i = 0; i < nums.length; i++) { - if (nums[i] == 1) { - count++; - } else { - if (count > max) { - max = count; - } - count = 0; - } - } - - if (count > max) { - max = count; - } - return max; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MinimumSizeSubarraySum.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MinimumSizeSubarraySum.java deleted file mode 100644 index a158052..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MinimumSizeSubarraySum.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【长度最小的子数组】 -// -// 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。 -// -// 示例: -// -// 输入: s = 7, nums = [2,3,1,2,4,3] -// 输出: 2 -// 解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。 -// 进阶: -// -// 如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。 - - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class MinimumSizeSubarraySum { - public static int minSubArrayLen(int s, int[] nums) { - if (nums == null || nums.length == 0) { - return 0; - } - - int j = 0, i = 0, sum = 0, min = Integer.MAX_VALUE; - - while (i < nums.length) { - sum += nums[i++]; - - while (sum >= s) { - min = Math.min(min, i - j); - sum -= nums[j++]; - } - } - - return min == Integer.MAX_VALUE ? 0 : min; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MoveZeros.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MoveZeros.java deleted file mode 100644 index c0b5b40..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/MoveZeros.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【移动零】 -// -// 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 -// -// 示例: -// -// 输入: [0,1,0,3,12] -// 输出: [1,3,12,0,0] -// 说明: -// -// 必须在原数组上操作,不能拷贝额外的数组。 -// 尽量减少操作次数。 - - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class MoveZeros { - private static void move(int[] nums, int pos) { - int temp = nums[pos]; - for (int i = pos; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - nums[nums.length - 1] = temp; - } - - public static void moveZeroes(int[] nums) { - int i = 0; - int right = nums.length - 1; - while (i <= right) { - if (nums[i] == 0) { - move(nums, i); - right--; - } else { - i++; - } - } - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PascalsTriangle.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PascalsTriangle.java deleted file mode 100644 index 1ee8ba5..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PascalsTriangle.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.dunwu.ds.array; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - - -// 【杨辉三角】 -// -// 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。 -// -// 在杨辉三角中,每个数是它左上方和右上方的数的和。 -// -// 示例: -// -// 输入: 5 -// 输出: -// [ -// [1], -// [1,1], -// [1,2,1], -// [1,3,3,1], -// [1,4,6,4,1] -// ] - - -/** - * @author Zhang Peng - * @date 2018-11-04 - */ -public class PascalsTriangle { - - public static List> generate(int numRows) { - List> result = new ArrayList<>(); - - if (numRows <= 0) { - - } else if (numRows == 1) { - result.add(Arrays.asList(1)); - } else if (numRows == 2) { - result.add(Arrays.asList(1)); - result.add(Arrays.asList(1, 1)); - } else { - result.add(Arrays.asList(1)); - result.add(Arrays.asList(1, 1)); - for (int i = 2; i < numRows; i++) { - List current = result.get(i - 1); - List next = new ArrayList<>(); - - for (int j = 0; j <= i; j++) { - if (j == 0 || j == i) { - next.add(1); - } else { - int x = current.get(j - 1); - int y = current.get(j); - next.add(x + y); - } - } - - result.add(next); - } - } - - return result; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PascalsTriangle2.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PascalsTriangle2.java deleted file mode 100644 index e133ce4..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PascalsTriangle2.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.dunwu.ds.array; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -// 【杨辉三角 II】 -// -// 给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。 -// -// 在杨辉三角中,每个数是它左上方和右上方的数的和。 -// -// 示例: -// -// 输入: 3 -// 输出: [1,3,3,1] -// 进阶: -// -// 你可以优化你的算法到 O(k) 空间复杂度吗? - - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class PascalsTriangle2 { - public static List getRow(int rowIndex) { - List> result = new ArrayList<>(); - - int rows = rowIndex + 1; - if (rows <= 0) { - - } else if (rows == 1) { - result.add(Arrays.asList(1)); - } else if (rows == 2) { - result.add(Arrays.asList(1)); - result.add(Arrays.asList(1, 1)); - } else { - result.add(Arrays.asList(1)); - result.add(Arrays.asList(1, 1)); - for (int i = 2; i < rows; i++) { - List current = result.get(i - 1); - List next = new ArrayList<>(); - - for (int j = 0; j <= i; j++) { - if (j == 0 || j == i) { - next.add(1); - } else { - int x = current.get(j - 1); - int y = current.get(j); - next.add(x + y); - } - } - - result.add(next); - } - } - - return result.get(rowIndex); - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PlusOne.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PlusOne.java deleted file mode 100644 index cce8401..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/PlusOne.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【加一】 -// -// 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 -// -// 最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。 -// -// 你可以假设除了整数 0 之外,这个整数不会以零开头。 -// -// 示例 1: -// -// 输入: [1,2,3] -// 输出: [1,2,4] -// 解释: 输入数组表示数字 123。 -// 示例 2: -// -// 输入: [4,3,2,1] -// 输出: [4,3,2,2] -// 解释: 输入数组表示数字 4321。 - - -import io.github.dunwu.ds.util.ArrayUtil; - -/** - * @author Zhang Peng - * @date 2018-11-04 - */ -public class PlusOne { - public static int[] plusOne(int[] digits) { - int n = digits.length; - for (int i = n - 1; i >= 0; i--) { - if (digits[i] < 9) { - digits[i]++; - return digits; - } - - digits[i] = 0; - } - - int[] newNumber = new int[n + 1]; - newNumber[0] = 1; - - return newNumber; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RemoveDuplicatesFromSortedArray.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RemoveDuplicatesFromSortedArray.java deleted file mode 100644 index 72d1201..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RemoveDuplicatesFromSortedArray.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【删除排序数组中的重复项】 -// -// 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 -// -// 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 -// -// 示例 1: -// -// 给定数组 nums = [1,1,2], -// -// 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 -// -// 你不需要考虑数组中超出新长度后面的元素。 -// 示例 2: -// -// 给定 nums = [0,0,1,1,1,2,2,3,3,4], -// -// 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 -// -// 你不需要考虑数组中超出新长度后面的元素。 -// 说明: -// -// 为什么返回数值是整数,但输出的答案是数组呢? -// -// 请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 -// -// 你可以想象内部操作如下: -// -// // nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 -// int len = removeDuplicates(nums); -// -// // 在函数里修改输入数组对于调用者是可见的。 -// // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 -// for (int i = 0; i < len; i++) { -// print(nums[i]); -// } - - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class RemoveDuplicatesFromSortedArray { - private static void remove(int[] nums, int pos) { - for (int i = pos; i < nums.length - 1; i++) { - nums[i] = nums[i + 1]; - } - } - - public static int removeDuplicates(int[] nums) { - int left = 0; - int right = nums.length - 1; - - while (left <= right) { - for (int i = left + 1; i <= right; i++) { - if (nums[i] == nums[left]) { - remove(nums, i); - right--; - i--; - } - } - left++; - } - - return right + 1; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RemoveElement.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RemoveElement.java deleted file mode 100644 index 071d847..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RemoveElement.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【移除元素】 -// -// 给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。 -// -// 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 -// -// 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 -// -// 示例 1: -// -// 给定 nums = [3,2,2,3], val = 3, -// -// 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 -// -// 你不需要考虑数组中超出新长度后面的元素。 -// 示例 2: -// -// 给定 nums = [0,1,2,2,3,0,4,2], val = 2, -// -// 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。 -// -// 注意这五个元素可为任意顺序。 -// -// 你不需要考虑数组中超出新长度后面的元素。 -// 说明: -// -// 为什么返回数值是整数,但输出的答案是数组呢? -// -// 请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 -// -// 你可以想象内部操作如下: -// -// // nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 -// int len = removeElement(nums, val); -// -// // 在函数里修改输入数组对于调用者是可见的。 -// // 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 -// for (int i = 0; i < len; i++) { -// print(nums[i]); -// } - - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class RemoveElement { - public static int removeElement(int[] nums, int val) { - int end = 0; - final int n = nums.length; - for (int i = 0; i < n; i++) { - if (nums[i] != val) { - nums[end] = nums[i]; - end++; - } - } - return end; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RotateArray.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RotateArray.java deleted file mode 100644 index c5be6ff..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/RotateArray.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【旋转数组】 -// -// 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。 -// -// 示例 1: -// -// 输入: [1,2,3,4,5,6,7] 和 k = 3 -// 输出: [5,6,7,1,2,3,4] -// 解释: -// 向右旋转 1 步: [7,1,2,3,4,5,6] -// 向右旋转 2 步: [6,7,1,2,3,4,5] -// 向右旋转 3 步: [5,6,7,1,2,3,4] -// 示例 2: -// -// 输入: [-1,-100,3,99] 和 k = 2 -// 输出: [3,99,-1,-100] -// 解释: -// 向右旋转 1 步: [99,-1,-100,3] -// 向右旋转 2 步: [3,99,-1,-100] -// 说明: -// -// 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。 -// 要求使用空间复杂度为 O(1) 的原地算法。 - - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class RotateArray { - public static void rotate(int[] nums, int k) { - int i = 0; - while (i < k) { - int j = nums.length - 1; - int temp = nums[nums.length - 1]; - while (j > 0) { - nums[j] = nums[j - 1]; - j--; - } - nums[0] = temp; - // System.out.println(ArrayUtil.getArrayString(nums, 0, nums.length - 1)); - i++; - } - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/SpiralMatrix.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/SpiralMatrix.java deleted file mode 100644 index ff56ba2..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/SpiralMatrix.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.dunwu.ds.array; - -import java.util.ArrayList; -import java.util.List; - - -// 【螺旋矩阵】 -// -// 给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。 -// -// 示例 1: -// -// 输入: -// [ -// [ 1, 2, 3 ], -// [ 4, 5, 6 ], -// [ 7, 8, 9 ] -// ] -// 输出: [1,2,3,6,9,8,7,4,5] -// 示例 2: -// -// 输入: -// [ -// [1, 2, 3, 4], -// [5, 6, 7, 8], -// [9,10,11,12] -// ] -// 输出: [1,2,3,4,8,12,11,10,9,5,6,7] - - -/** - * @author Zhang Peng - * @date 2018-11-04 - */ -public class SpiralMatrix { - public static List spiralOrder(int[][] matrix) { - ArrayList list = new ArrayList<>(); - if (matrix.length == 0) { - return list; - } - - final int M = matrix.length; - final int N = matrix[0].length; - final int MAX = M * N; - int x = 0, y = 0; - int XMIN = 0, YMIN = 0; - int XMAX = M - 1, YMAX = N - 1; - for (int index = 0; index < MAX; index++) { - list.add(matrix[x][y]); - - if (x == XMIN && y != YMAX) { - y++; - } else if (y == YMAX && x != XMAX) { - x++; - } else if (x == XMAX && y != YMIN) { - y--; - } else if (y == YMIN && x != XMIN + 1) { - x--; - } else if (x == XMIN + 1 && y == YMIN) { - XMIN = XMIN + 1; - YMIN = YMIN + 1; - XMAX = XMAX - 1; - YMAX = YMAX - 1; - x = XMIN; - y = YMIN; - } - } - - return list; - } - - public static void main(String[] args) { - int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; - int[][] matrix2 = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; - List results = spiralOrder(matrix); - System.out.println(); - System.out.println(); - List results2 = spiralOrder(matrix2); - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/TwoDimensionArray.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/TwoDimensionArray.java deleted file mode 100644 index 5af8510..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/TwoDimensionArray.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.dunwu.ds.array; - -/** - * @author Zhang Peng - * @date 2018-11-04 - */ -public class TwoDimensionArray { - private static void printArray(int[][] a) { - for (int i = 0; i < a.length; ++i) { - System.out.println(a[i]); - } - for (int i = 0; i < a.length; ++i) { - for (int j = 0; a[i] != null && j < a[i].length; ++j) { - System.out.print(a[i][j] + " "); - } - System.out.println(); - } - } - - public static void main(String[] args) { - System.out.println("Example I:"); - int[][] a = new int[2][5]; - printArray(a); - System.out.println("Example II:"); - int[][] b = new int[2][]; - printArray(b); - System.out.println("Example III:"); - b[0] = new int[3]; - b[1] = new int[5]; - printArray(b); - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/TwoSum2InputArrayIsSorted.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/array/TwoSum2InputArrayIsSorted.java deleted file mode 100644 index cd9901a..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/array/TwoSum2InputArrayIsSorted.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.github.dunwu.ds.array; - -// 【两数之和 II - 输入有序数组】 -// -// 给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 -// -// 函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 -// -// 说明: -// -// 返回的下标值(index1 和 index2)不是从零开始的。 -// 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 -// 示例: -// -// 输入: numbers = [2, 7, 11, 15], target = 9 -// 输出: [1,2] -// 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 - - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class TwoSum2InputArrayIsSorted { - public static int[] twoSum(int[] numbers, int target) { - int[] indice = new int[2]; - if (numbers == null || numbers.length < 2) { - return indice; - } - - int left = 0, right = numbers.length - 1; - while (left < right) { - int v = numbers[left] + numbers[right]; - if (v == target) { - indice[0] = left + 1; - indice[1] = right + 1; - break; - } else if (v > target) { - right--; - } else { - left++; - } - } - return indice; - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/tree/BinaryTree.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/tree/BinaryTree.java deleted file mode 100644 index b777249..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/tree/BinaryTree.java +++ /dev/null @@ -1,910 +0,0 @@ -package io.github.dunwu.ds.tree; - -import io.github.dunwu.ds.common.ITree; - -import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Deque; - -/** - * B树是一种树数据结构,可以对数据进行排序,并允许以对数时间进行搜索,顺序访问,插入和删除。 - *

- * B树是二叉搜索树的一般化,因为节点可以有两个以上的子节点。 - *

- * 与自平衡二进制搜索树不同,B树针对读取和写入大块数据的系统进行了优化。 - *

- * 它通常用于数据库和文件系统。 - *

- * @author Justin Wetherell - * @see B-Tree (Wikipedia) - */ -@SuppressWarnings("ALL") -public class BinaryTree> implements ITree { - - private int minKeySize = 1; - private int minChildrenSize = minKeySize + 1; // 2 - private int maxKeySize = 2 * minKeySize; // 2 - private int maxChildrenSize = maxKeySize + 1; // 3 - - private Node root = null; - private int size = 0; - - /** - * Constructor for B-Tree which defaults to a 2-3 B-Tree. - */ - public BinaryTree() {} - - /** - * Constructor for B-Tree of ordered parameter. Order here means minimum number of keys in a non-root node. - * @param order of the B-Tree. - */ - public BinaryTree(int order) { - this.minKeySize = order; - this.minChildrenSize = minKeySize + 1; - this.maxKeySize = 2 * minKeySize; - this.maxChildrenSize = maxKeySize + 1; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean add(T value) { - if (root == null) { - root = new Node(null, maxKeySize, maxChildrenSize); - root.addKey(value); - } else { - Node node = root; - while (node != null) { - if (node.numberOfChildren() == 0) { - node.addKey(value); - if (node.numberOfKeys() <= maxKeySize) { - // A-OK - break; - } - // Need to split up - split(node); - break; - } - // Navigate - - // Lesser or equal - T lesser = node.getKey(0); - if (value.compareTo(lesser) <= 0) { - node = node.getChild(0); - continue; - } - - // Greater - int numberOfKeys = node.numberOfKeys(); - int last = numberOfKeys - 1; - T greater = node.getKey(last); - if (value.compareTo(greater) > 0) { - node = node.getChild(numberOfKeys); - continue; - } - - // Search internal nodes - for (int i = 1; i < node.numberOfKeys(); i++) { - T prev = node.getKey(i - 1); - T next = node.getKey(i); - if (value.compareTo(prev) > 0 && value.compareTo(next) <= 0) { - node = node.getChild(i); - break; - } - } - } - } - - size++; - - return true; - } - - /** - * The node's key size is greater than maxKeySize, split down the middle. - * @param nodeToSplit to split. - */ - private void split(Node nodeToSplit) { - Node node = nodeToSplit; - int numberOfKeys = node.numberOfKeys(); - int medianIndex = numberOfKeys / 2; - T medianValue = node.getKey(medianIndex); - - Node left = new Node(null, maxKeySize, maxChildrenSize); - for (int i = 0; i < medianIndex; i++) { - left.addKey(node.getKey(i)); - } - if (node.numberOfChildren() > 0) { - for (int j = 0; j <= medianIndex; j++) { - Node c = node.getChild(j); - left.addChild(c); - } - } - - Node right = new Node(null, maxKeySize, maxChildrenSize); - for (int i = medianIndex + 1; i < numberOfKeys; i++) { - right.addKey(node.getKey(i)); - } - if (node.numberOfChildren() > 0) { - for (int j = medianIndex + 1; j < node.numberOfChildren(); j++) { - Node c = node.getChild(j); - right.addChild(c); - } - } - - if (node.parent == null) { - // new root, height of tree is increased - Node newRoot = new Node(null, maxKeySize, maxChildrenSize); - newRoot.addKey(medianValue); - node.parent = newRoot; - root = newRoot; - node = root; - node.addChild(left); - node.addChild(right); - } else { - // Move the median value up to the parent - Node parent = node.parent; - parent.addKey(medianValue); - parent.removeChild(node); - parent.addChild(left); - parent.addChild(right); - - if (parent.numberOfKeys() > maxKeySize) { - split(parent); - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public T remove(T value) { - T removed = null; - Node node = this.getNode(value); - removed = remove(value, node); - return removed; - } - - /** - * Remove the value from the Node and check invariants - * @param value T to remove from the tree - * @param node Node to remove value from - * @return True if value was removed from the tree. - */ - private T remove(T value, Node node) { - if (node == null) { - return null; - } - - T removed = null; - int index = node.indexOf(value); - removed = node.removeKey(value); - if (node.numberOfChildren() == 0) { - // leaf node - if (node.parent != null && node.numberOfKeys() < minKeySize) { - this.combined(node); - } else if (node.parent == null && node.numberOfKeys() == 0) { - // Removing root node with no keys or children - root = null; - } - } else { - // internal node - Node lesser = node.getChild(index); - Node greatest = this.getGreatestNode(lesser); - T replaceValue = this.removeGreatestValue(greatest); - node.addKey(replaceValue); - if (greatest.parent != null && greatest.numberOfKeys() < minKeySize) { - this.combined(greatest); - } - if (greatest.numberOfChildren() > maxChildrenSize) { - this.split(greatest); - } - } - - size--; - - return removed; - } - - /** - * Remove greatest valued key from node. - * @param node to remove greatest value from. - * @return value removed; - */ - private T removeGreatestValue(Node node) { - T value = null; - if (node.numberOfKeys() > 0) { - value = node.removeKey(node.numberOfKeys() - 1); - } - return value; - } - - /** - * {@inheritDoc} - */ - @Override - public void clear() { - root = null; - size = 0; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean contains(T value) { - Node node = getNode(value); - return (node != null); - } - - /** - * Get the node with value. - * @param value to find in the tree. - * @return Node with value. - */ - private Node getNode(T value) { - Node node = root; - while (node != null) { - T lesser = node.getKey(0); - if (value.compareTo(lesser) < 0) { - if (node.numberOfChildren() > 0) { - node = node.getChild(0); - } else { - node = null; - } - continue; - } - - int numberOfKeys = node.numberOfKeys(); - int last = numberOfKeys - 1; - T greater = node.getKey(last); - if (value.compareTo(greater) > 0) { - if (node.numberOfChildren() > numberOfKeys) { - node = node.getChild(numberOfKeys); - } else { - node = null; - } - continue; - } - - for (int i = 0; i < numberOfKeys; i++) { - T currentValue = node.getKey(i); - if (currentValue.compareTo(value) == 0) { - return node; - } - - int next = i + 1; - if (next <= last) { - T nextValue = node.getKey(next); - if (currentValue.compareTo(value) < 0 && nextValue.compareTo(value) > 0) { - if (next < node.numberOfChildren()) { - node = node.getChild(next); - break; - } - return null; - } - } - } - } - return null; - } - - /** - * Get the greatest valued child from node. - * @param nodeToGet child with the greatest value. - * @return Node child with greatest value. - */ - private Node getGreatestNode(Node nodeToGet) { - Node node = nodeToGet; - while (node.numberOfChildren() > 0) { - node = node.getChild(node.numberOfChildren() - 1); - } - return node; - } - - /** - * Combined children keys with parent when size is less than minKeySize. - * @param node with children to combined. - * @return True if combined successfully. - */ - private boolean combined(Node node) { - Node parent = node.parent; - int index = parent.indexOf(node); - int indexOfLeftNeighbor = index - 1; - int indexOfRightNeighbor = index + 1; - - Node rightNeighbor = null; - int rightNeighborSize = -minChildrenSize; - if (indexOfRightNeighbor < parent.numberOfChildren()) { - rightNeighbor = parent.getChild(indexOfRightNeighbor); - rightNeighborSize = rightNeighbor.numberOfKeys(); - } - - // Try to borrow neighbor - if (rightNeighbor != null && rightNeighborSize > minKeySize) { - // Try to borrow from right neighbor - T removeValue = rightNeighbor.getKey(0); - int prev = getIndexOfPreviousValue(parent, removeValue); - T parentValue = parent.removeKey(prev); - T neighborValue = rightNeighbor.removeKey(0); - node.addKey(parentValue); - parent.addKey(neighborValue); - if (rightNeighbor.numberOfChildren() > 0) { - node.addChild(rightNeighbor.removeChild(0)); - } - } else { - Node leftNeighbor = null; - int leftNeighborSize = -minChildrenSize; - if (indexOfLeftNeighbor >= 0) { - leftNeighbor = parent.getChild(indexOfLeftNeighbor); - leftNeighborSize = leftNeighbor.numberOfKeys(); - } - - if (leftNeighbor != null && leftNeighborSize > minKeySize) { - // Try to borrow from left neighbor - T removeValue = leftNeighbor.getKey(leftNeighbor.numberOfKeys() - 1); - int prev = getIndexOfNextValue(parent, removeValue); - T parentValue = parent.removeKey(prev); - T neighborValue = leftNeighbor.removeKey(leftNeighbor.numberOfKeys() - 1); - node.addKey(parentValue); - parent.addKey(neighborValue); - if (leftNeighbor.numberOfChildren() > 0) { - node.addChild(leftNeighbor.removeChild(leftNeighbor.numberOfChildren() - 1)); - } - } else if (rightNeighbor != null && parent.numberOfKeys() > 0) { - // Can't borrow from neighbors, try to combined with right neighbor - T removeValue = rightNeighbor.getKey(0); - int prev = getIndexOfPreviousValue(parent, removeValue); - T parentValue = parent.removeKey(prev); - parent.removeChild(rightNeighbor); - node.addKey(parentValue); - for (int i = 0; i < rightNeighbor.keysSize; i++) { - T v = rightNeighbor.getKey(i); - node.addKey(v); - } - for (int i = 0; i < rightNeighbor.childrenSize; i++) { - Node c = rightNeighbor.getChild(i); - node.addChild(c); - } - - if (parent.parent != null && parent.numberOfKeys() < minKeySize) { - // removing key made parent too small, combined up tree - this.combined(parent); - } else if (parent.numberOfKeys() == 0) { - // parent no longer has keys, make this node the new root - // which decreases the height of the tree - node.parent = null; - root = node; - } - } else if (leftNeighbor != null && parent.numberOfKeys() > 0) { - // Can't borrow from neighbors, try to combined with left neighbor - T removeValue = leftNeighbor.getKey(leftNeighbor.numberOfKeys() - 1); - int prev = getIndexOfNextValue(parent, removeValue); - T parentValue = parent.removeKey(prev); - parent.removeChild(leftNeighbor); - node.addKey(parentValue); - for (int i = 0; i < leftNeighbor.keysSize; i++) { - T v = leftNeighbor.getKey(i); - node.addKey(v); - } - for (int i = 0; i < leftNeighbor.childrenSize; i++) { - Node c = leftNeighbor.getChild(i); - node.addChild(c); - } - - if (parent.parent != null && parent.numberOfKeys() < minKeySize) { - // removing key made parent too small, combined up tree - this.combined(parent); - } else if (parent.numberOfKeys() == 0) { - // parent no longer has keys, make this node the new root - // which decreases the height of the tree - node.parent = null; - root = node; - } - } - } - - return true; - } - - /** - * Get the index of previous key in node. - * @param node to find the previous key in. - * @param value to find a previous value for. - * @return index of previous key or -1 if not found. - */ - private int getIndexOfPreviousValue(Node node, T value) { - for (int i = 1; i < node.numberOfKeys(); i++) { - T t = node.getKey(i); - if (t.compareTo(value) >= 0) { - return i - 1; - } - } - return node.numberOfKeys() - 1; - } - - /** - * Get the index of next key in node. - * @param node to find the next key in. - * @param value to find a next value for. - * @return index of next key or -1 if not found. - */ - private int getIndexOfNextValue(Node node, T value) { - for (int i = 0; i < node.numberOfKeys(); i++) { - T t = node.getKey(i); - if (t.compareTo(value) >= 0) { - return i; - } - } - return node.numberOfKeys() - 1; - } - - /** - * {@inheritDoc} - */ - @Override - public int size() { - return size; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean validate() { - if (root == null) { - return true; - } - return validateNode(root); - } - - /** - * Validate the node according to the B-Tree invariants. - * @param node to validate. - * @return True if valid. - */ - private boolean validateNode(Node node) { - int keySize = node.numberOfKeys(); - if (keySize > 1) { - // Make sure the keys are sorted - for (int i = 1; i < keySize; i++) { - T p = node.getKey(i - 1); - T n = node.getKey(i); - if (p.compareTo(n) > 0) { - return false; - } - } - } - int childrenSize = node.numberOfChildren(); - if (node.parent == null) { - // root - if (keySize > maxKeySize) { - // check max key size. root does not have a min key size - return false; - } else if (childrenSize == 0) { - // if root, no children, and keys are valid - return true; - } else if (childrenSize < minChildrenSize) { - // root should have zero or at least two children - return false; - } else if (childrenSize > maxChildrenSize) { - return false; - } - } else { - // non-root - if (keySize < minKeySize) { - return false; - } else if (keySize > maxKeySize) { - return false; - } else if (childrenSize == 0) { - return true; - } else if (keySize != (childrenSize - 1)) { - // If there are chilren, there should be one more child then - // keys - return false; - } else if (childrenSize < minChildrenSize) { - return false; - } else if (childrenSize > maxChildrenSize) { - return false; - } - } - - Node first = node.getChild(0); - // The first child's last key should be less than the node's first key - if (first.getKey(first.numberOfKeys() - 1).compareTo(node.getKey(0)) > 0) { - return false; - } - - Node last = node.getChild(node.numberOfChildren() - 1); - // The last child's first key should be greater than the node's last key - if (last.getKey(0).compareTo(node.getKey(node.numberOfKeys() - 1)) < 0) { - return false; - } - - // Check that each node's first and last key holds it's invariance - for (int i = 1; i < node.numberOfKeys(); i++) { - T p = node.getKey(i - 1); - T n = node.getKey(i); - Node c = node.getChild(i); - if (p.compareTo(c.getKey(0)) > 0) { - return false; - } - if (n.compareTo(c.getKey(c.numberOfKeys() - 1)) < 0) { - return false; - } - } - - for (int i = 0; i < node.childrenSize; i++) { - Node c = node.getChild(i); - boolean valid = this.validateNode(c); - if (!valid) { - return false; - } - } - - return true; - } - - /** - * {@inheritDoc} - */ - @Override - public java.util.Collection toCollection() { - return (new JavaCompatibleBinaryTree(this)); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return TreePrinter.getString(this); - } - - private static class Node> { - - private T[] keys = null; - private int keysSize = 0; - private Node[] children = null; - private int childrenSize = 0; - private Comparator> comparator = new Comparator>() { - @Override - public int compare(Node arg0, Node arg1) { - return arg0.getKey(0).compareTo(arg1.getKey(0)); - } - }; - - protected Node parent = null; - - private Node(Node parent, int maxKeySize, int maxChildrenSize) { - this.parent = parent; - this.keys = (T[]) new Comparable[maxKeySize + 1]; - this.keysSize = 0; - this.children = new Node[maxChildrenSize + 1]; - this.childrenSize = 0; - } - - private T getKey(int index) { - return keys[index]; - } - - private int indexOf(T value) { - for (int i = 0; i < keysSize; i++) { - if (keys[i].equals(value)) { - return i; - } - } - return -1; - } - - private void addKey(T value) { - keys[keysSize++] = value; - Arrays.sort(keys, 0, keysSize); - } - - private T removeKey(T value) { - T removed = null; - boolean found = false; - if (keysSize == 0) { - return null; - } - for (int i = 0; i < keysSize; i++) { - if (keys[i].equals(value)) { - found = true; - removed = keys[i]; - } else if (found) { - // shift the rest of the keys down - keys[i - 1] = keys[i]; - } - } - if (found) { - keysSize--; - keys[keysSize] = null; - } - return removed; - } - - private T removeKey(int index) { - if (index >= keysSize) { - return null; - } - T value = keys[index]; - for (int i = index + 1; i < keysSize; i++) { - // shift the rest of the keys down - keys[i - 1] = keys[i]; - } - keysSize--; - keys[keysSize] = null; - return value; - } - - private int numberOfKeys() { - return keysSize; - } - - private Node getChild(int index) { - if (index >= childrenSize) { - return null; - } - return children[index]; - } - - private int indexOf(Node child) { - for (int i = 0; i < childrenSize; i++) { - if (children[i].equals(child)) { - return i; - } - } - return -1; - } - - private boolean addChild(Node child) { - child.parent = this; - children[childrenSize++] = child; - Arrays.sort(children, 0, childrenSize, comparator); - return true; - } - - private boolean removeChild(Node child) { - boolean found = false; - if (childrenSize == 0) { - return found; - } - for (int i = 0; i < childrenSize; i++) { - if (children[i].equals(child)) { - found = true; - } else if (found) { - // shift the rest of the keys down - children[i - 1] = children[i]; - } - } - if (found) { - childrenSize--; - children[childrenSize] = null; - } - return found; - } - - private Node removeChild(int index) { - if (index >= childrenSize) { - return null; - } - Node value = children[index]; - children[index] = null; - for (int i = index + 1; i < childrenSize; i++) { - // shift the rest of the keys down - children[i - 1] = children[i]; - } - childrenSize--; - children[childrenSize] = null; - return value; - } - - private int numberOfChildren() { - return childrenSize; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - - builder.append("keys=["); - for (int i = 0; i < numberOfKeys(); i++) { - T value = getKey(i); - builder.append(value); - if (i < numberOfKeys() - 1) { - builder.append(", "); - } - } - builder.append("]\n"); - - if (parent != null) { - builder.append("parent=["); - for (int i = 0; i < parent.numberOfKeys(); i++) { - T value = parent.getKey(i); - builder.append(value); - if (i < parent.numberOfKeys() - 1) { - builder.append(", "); - } - } - builder.append("]\n"); - } - - if (children != null) { - builder.append("keySize=").append(numberOfKeys()).append(" children=").append(numberOfChildren()) - .append("\n"); - } - - return builder.toString(); - } - } - - - private static class TreePrinter { - - public static > String getString(BinaryTree tree) { - if (tree.root == null) { - return "Tree has no nodes."; - } - return getString(tree.root, "", true); - } - - private static > String getString(Node node, String prefix, boolean isTail) { - StringBuilder builder = new StringBuilder(); - - builder.append(prefix).append((isTail ? "└── " : "├── ")); - for (int i = 0; i < node.numberOfKeys(); i++) { - T value = node.getKey(i); - builder.append(value); - if (i < node.numberOfKeys() - 1) { - builder.append(", "); - } - } - builder.append("\n"); - - if (node.children != null) { - for (int i = 0; i < node.numberOfChildren() - 1; i++) { - Node obj = node.getChild(i); - builder.append(getString(obj, prefix + (isTail ? " " : "│ "), false)); - } - if (node.numberOfChildren() >= 1) { - Node obj = node.getChild(node.numberOfChildren() - 1); - builder.append(getString(obj, prefix + (isTail ? " " : "│ "), true)); - } - } - - return builder.toString(); - } - } - - - public static class JavaCompatibleBinaryTree> extends java.util.AbstractCollection { - - private BinaryTree tree = null; - - public JavaCompatibleBinaryTree(BinaryTree tree) { - this.tree = tree; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean add(T value) { - return tree.add(value); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean remove(Object value) { - return (tree.remove((T) value) != null); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean contains(Object value) { - return tree.contains((T) value); - } - - /** - * {@inheritDoc} - */ - @Override - public int size() { - return tree.size(); - } - - /** - * {@inheritDoc} - */ - @Override - public java.util.Iterator iterator() { - return (new BinaryTreeIterator(this.tree)); - } - - private static class BinaryTreeIterator> implements java.util.Iterator { - - private BinaryTree tree = null; - private Node lastNode = null; - private C lastValue = null; - private int index = 0; - private Deque> toVisit = new ArrayDeque>(); - - protected BinaryTreeIterator(BinaryTree tree) { - this.tree = tree; - if (tree.root != null && tree.root.keysSize > 0) { - toVisit.add(tree.root); - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean hasNext() { - boolean toVisitSizeNotZero = toVisit.size() > 0; - boolean lastNodeNotZero = lastNode != null && index < lastNode.keysSize; - if (lastNodeNotZero || toVisitSizeNotZero) { - return true; - } - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public C next() { - if (lastNode != null && (index < lastNode.keysSize)) { - lastValue = lastNode.getKey(index++); - return lastValue; - } - while (toVisit.size() > 0) { - // Go thru the current nodes - Node n = toVisit.pop(); - - // Add non-null children - for (int i = 0; i < n.childrenSize; i++) { - toVisit.add(n.getChild(i)); - } - - // Update last node (used in remove method) - index = 0; - lastNode = n; - lastValue = lastNode.getKey(index++); - return lastValue; - } - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public void remove() { - if (lastNode != null && lastValue != null) { - // On remove, reset the iterator (very inefficient, I know) - tree.remove(lastValue, lastNode); - - lastNode = null; - lastValue = null; - index = 0; - toVisit.clear(); - if (tree.root != null && tree.root.keysSize > 0) { - toVisit.add(tree.root); - } - } - } - } - } -} diff --git a/codes/data-structure/src/main/java/io/github/dunwu/ds/util/ArrayUtil.java b/codes/data-structure/src/main/java/io/github/dunwu/ds/util/ArrayUtil.java deleted file mode 100644 index 1f0f0b6..0000000 --- a/codes/data-structure/src/main/java/io/github/dunwu/ds/util/ArrayUtil.java +++ /dev/null @@ -1,171 +0,0 @@ -package io.github.dunwu.ds.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Random; - -/** - * @author Zhang Peng - */ -public class ArrayUtil { - private static final Logger logger = LoggerFactory.getLogger(ArrayUtil.class); - - public static void debugLogArray(T[] list, int begin, int end, String tip) { - String content = tip + getArrayString(list, begin, end); - if (logger.isDebugEnabled()) { - logger.debug(content); - } - } - - public static String getArrayString(T[] list, int begin, int end) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < begin; i++) { - sb.append("\t"); - } - int count = 0; - for (int i = begin; i <= end; i++) { - sb.append(list[i] + "\t"); - if (++count == 10) { - sb.append("\n"); - count = 0; - } - } - - return sb.toString(); - } - - public static String getArrayString(int[] list, int begin, int end) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < begin; i++) { - sb.append("\t"); - } - int count = 0; - for (int i = begin; i < end; i++) { - sb.append(list[i] + "\t"); - if (++count == 10) { - sb.append("\n"); - count = 0; - } - } - sb.append(list[end]); - - return sb.toString(); - } - - /** - * 随机指定范围内N个不重复的Int数组。 - *

在初始化的无重复待选数组中随机产生一个数放入结果中,

- *

将待选数组被随机到的数,用待选数组(len-1)下标对应的数替换,

- *

然后从len-2里随机产生下一个随机数,如此类推

- * @param min 指定范围最小值 - * @param max 指定范围最大值 - * @param length 随机数个数 - * @return int[] 随机数结果集 - */ - public static int[] randomNoRepeatIntArray(int min, int max, int length) { - int len = max - min + 1; - - if (max < min || length > len) { - return null; - } - - // 初始化给定范围的待选数组 - int[] source = new int[len]; - for (int i = min; i < min + len; i++) { - source[i - min] = i; - } - - int[] result = new int[length]; - Random rd = new Random(); - int index = 0; - for (int i = 0; i < result.length; i++) { - // 待选数组0到(len-2)随机一个下标 - index = Math.abs(rd.nextInt() % len--); - // 将随机到的数放入结果集 - result[i] = source[index]; - // 将待选数组中被随机到的数,用待选数组(len-1)下标对应的数替换 - source[index] = source[len]; - } - return result; - } - - /** - * 随机指定范围内N个重复的Int数组。 - * @param min 指定范围最小值 - * @param max 指定范围最大值 - * @param length 随机数个数 - * @return 随机数结果集 - */ - public static int[] randomRepeatIntArray(int min, int max, int length) { - int len = max - min + 1; - - if (max < min || length > len) { - return null; - } - - int[] result = new int[length]; - for (int i = 0; i < result.length; i++) { - result[i] = (int) (Math.random() * max); - } - return result; - } - - /** - * 随机指定范围内N个不重复的Integer数组。 - *

在初始化的无重复待选数组中随机产生一个数放入结果中,

- *

将待选数组被随机到的数,用待选数组(len-1)下标对应的数替换,

- *

然后从len-2里随机产生下一个随机数,如此类推

- * @param min 指定范围最小值 - * @param max 指定范围最大值 - * @param length 随机数个数 - * @return int[] 随机数结果集 - */ - public static Integer[] randomNoRepeatIntegerArray(int min, int max, int length) { - int len = max - min + 1; - - if (max < min || length > len) { - return null; - } - - // 初始化给定范围的待选数组 - Integer[] source = new Integer[len]; - for (int i = min; i < min + len; i++) { - source[i - min] = i; - } - - Integer[] result = new Integer[length]; - Random rd = new Random(); - int index = 0; - for (int i = 0; i < result.length; i++) { - // 待选数组0到(len-2)随机一个下标 - index = Math.abs(rd.nextInt() % len--); - // 将随机到的数放入结果集 - result[i] = source[index]; - // 将待选数组中被随机到的数,用待选数组(len-1)下标对应的数替换 - source[index] = source[len]; - } - return result; - } - - /** - * 随机指定范围内N个重复的Integer数组。 - * @param min 指定范围最小值 - * @param max 指定范围最大值 - * @param length 随机数个数 - * @return 随机数结果集 - */ - public static Integer[] randomRepeatIntegerArray(int min, int max, int length) { - int len = max - min + 1; - - if (max < min || length > len) { - return null; - } - - Integer[] result = new Integer[length]; - for (int i = 0; i < result.length; i++) { - result[i] = (int) (Math.random() * max); - } - return result; - } -} diff --git a/codes/data-structure/src/main/resources/logback.xml b/codes/data-structure/src/main/resources/logback.xml deleted file mode 100644 index 915aa83..0000000 --- a/codes/data-structure/src/main/resources/logback.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - ${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log - 30 - - - - - 30MB - - - - %d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n - - - - - - - - - - - - - - - - - diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/ArrayPartitionTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/ArrayPartitionTest.java deleted file mode 100644 index 9994a88..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/ArrayPartitionTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class ArrayPartitionTest { - - @Test - public void test() { - int[] nums1 = {1, 4, 3, 2}; - Assert.assertEquals(4, ArrayPartition.arrayPairSum(nums1)); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/DiagonalTraverseTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/DiagonalTraverseTest.java deleted file mode 100644 index f11639e..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/DiagonalTraverseTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class DiagonalTraverseTest { - @Test - public void test() { - int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; - int[] expected = {1, 2, 4, 7, 5, 3, 6, 8, 9}; - Assert.assertArrayEquals(expected, DiagonalTraverse.findDiagonalOrder(matrix)); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/FindPivotIndexTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/FindPivotIndexTest.java deleted file mode 100644 index 295deab..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/FindPivotIndexTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class FindPivotIndexTest { - @Test - public void test() { - Assert.assertEquals(3, FindPivotIndex.pivotIndex(new int[] {1, 7, 3, 6, 5, 6})); - Assert.assertEquals(-1, FindPivotIndex.pivotIndex(new int[] {1, 2, 3})); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/LargestNumberAtLeastTwiceOfOthersTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/LargestNumberAtLeastTwiceOfOthersTest.java deleted file mode 100644 index a48db29..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/LargestNumberAtLeastTwiceOfOthersTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class LargestNumberAtLeastTwiceOfOthersTest { - @Test - public void test() { - int[] nums1 = {3, 6, 1, 0}; - int[] nums2 = {1, 2, 3, 4}; - - Assert.assertEquals(1, LargestNumberAtLeastTwiceOfOthers.dominantIndex(nums1)); - Assert.assertEquals(-1, LargestNumberAtLeastTwiceOfOthers.dominantIndex(nums2)); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/MaxConsecutiveOnesTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/MaxConsecutiveOnesTest.java deleted file mode 100644 index b28b4a2..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/MaxConsecutiveOnesTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class MaxConsecutiveOnesTest { - @Test - public void test() { - Assert.assertEquals(3, MaxConsecutiveOnes.findMaxConsecutiveOnes(new int[] {1, 1, 0, 1, 1, 1})); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/MinimumSizeSubarraySumTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/MinimumSizeSubarraySumTest.java deleted file mode 100644 index 38670e5..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/MinimumSizeSubarraySumTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class MinimumSizeSubarraySumTest { - @Test - public void test() { - MinimumSizeSubarraySum.minSubArrayLen(7, new int[] {2, 3, 1, 2, 4, 3}); - MinimumSizeSubarraySum.minSubArrayLen(11, new int[] {2, 3, 1, 2, 4, 3}); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/MoveZerosTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/MoveZerosTest.java deleted file mode 100644 index e033e90..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/MoveZerosTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class MoveZerosTest { - @Test - public void test() { - int[] nums1 = {0, 1, 0, 3, 12}; - MoveZeros.moveZeroes(nums1); - Assert.assertArrayEquals(new int[] {1, 3, 12, 0, 0}, nums1); - - int[] nums2 = {0, 0, 1}; - MoveZeros.moveZeroes(nums2); - Assert.assertArrayEquals(new int[] {1, 0, 0}, nums2); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/PascalsTriangle2Test.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/PascalsTriangle2Test.java deleted file mode 100644 index 1055923..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/PascalsTriangle2Test.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.dunwu.ds.array; - -import io.github.dunwu.ds.util.ArrayUtil; -import org.junit.Test; - -import java.util.List; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class PascalsTriangle2Test { - @Test - public void test() { - List list = PascalsTriangle2.getRow(3); - System.out.println(ArrayUtil.getArrayString(list.toArray(), 0, list.size() - 1)); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/PascalsTriangleTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/PascalsTriangleTest.java deleted file mode 100644 index 28f03a5..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/PascalsTriangleTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class PascalsTriangleTest { - void printPascalsTriangle(List> lists) { - System.out.printf("【%d层杨辉三角】\n", lists.size()); - for (List list : lists) { - for (Integer num : list) { - System.out.print(num + "\t"); - } - System.out.println(); - } - System.out.println(); - } - - @Test - public void test() { - List> lists = PascalsTriangle.generate(5); - printPascalsTriangle(lists); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/PlusOneTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/PlusOneTest.java deleted file mode 100644 index 5401e94..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/PlusOneTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.dunwu.ds.array; - -import io.github.dunwu.ds.util.ArrayUtil; -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class PlusOneTest { - @Test - public void test() { - int[] nums1 = {1, 2, 3}; - int[] nums2 = {4, 3, 2, 1}; - int[] nums3 = {9, 9, 9, 9}; - - int[] expected1 = {1, 2, 4}; - int[] expected2 = {4, 3, 2, 2}; - int[] expected3 = {1, 0, 0, 0, 0}; - - Assert.assertArrayEquals(expected1, PlusOne.plusOne(nums1)); - Assert.assertArrayEquals(expected2, PlusOne.plusOne(nums2)); - Assert.assertArrayEquals(expected3, PlusOne.plusOne(nums3)); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/RemoveDuplicatesFromSortedArrayTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/RemoveDuplicatesFromSortedArrayTest.java deleted file mode 100644 index 48f7e1d..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/RemoveDuplicatesFromSortedArrayTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class RemoveDuplicatesFromSortedArrayTest { - @Test - public void test() { - int[] nums1 = {1, 1, 2}; - Assert.assertEquals(2, RemoveDuplicatesFromSortedArray.removeDuplicates(nums1)); - - int[] nums2 = {0, 0, 1, 1, 1, 2, 2, 3, 3, 4}; - Assert.assertEquals(5, RemoveDuplicatesFromSortedArray.removeDuplicates(nums2)); - - int[] nums3 = {1, 2}; - Assert.assertEquals(2, RemoveDuplicatesFromSortedArray.removeDuplicates(nums3)); - - int[] nums4 = {2, 2}; - Assert.assertEquals(1, RemoveDuplicatesFromSortedArray.removeDuplicates(nums4)); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/RemoveElementTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/RemoveElementTest.java deleted file mode 100644 index 3c3f307..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/RemoveElementTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class RemoveElementTest { - @Test - public void test() { - int[] nums1 = {3, 2, 2, 3}; - Assert.assertEquals(2, RemoveElement.removeElement(nums1, 3)); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/RotateArrayTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/RotateArrayTest.java deleted file mode 100644 index a652832..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/RotateArrayTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class RotateArrayTest { - @Test - public void test() { - int[] nums1 = {1, 2, 3, 4, 5, 6, 7}; - int[] expected1 = {5, 6, 7, 1, 2, 3, 4}; - RotateArray.rotate(nums1, 3); - Assert.assertArrayEquals(expected1, nums1); - - int[] nums2 = {-1, -100, 3, 99}; - int[] expected2 = {3, 99, -1, -100}; - RotateArray.rotate(nums2, 2); - Assert.assertArrayEquals(expected2, nums2); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/SpiralMatrixTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/SpiralMatrixTest.java deleted file mode 100644 index 77b3300..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/SpiralMatrixTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.dunwu.ds.array; - -import io.github.dunwu.ds.util.ArrayUtil; -import org.junit.Assert; -import org.junit.Test; - -import java.util.List; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class SpiralMatrixTest { - @Test - public void test() { - int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; - int[][] matrix2 = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}; - - Integer[] expected = {1, 2, 3, 6, 9, 8, 7, 4, 5}; - Integer[] expected2 = {1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10}; - - List results = SpiralMatrix.spiralOrder(matrix); - List results2 = SpiralMatrix.spiralOrder(matrix2); - - Assert.assertArrayEquals(expected, results.toArray()); - Assert.assertArrayEquals(expected2, results2.toArray()); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/TwoSum2InputArrayIsSortedTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/array/TwoSum2InputArrayIsSortedTest.java deleted file mode 100644 index 14fae27..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/array/TwoSum2InputArrayIsSortedTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.dunwu.ds.array; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class TwoSum2InputArrayIsSortedTest { - @Test - public void test() { - Assert.assertArrayEquals(new int[] {1, 2}, TwoSum2InputArrayIsSorted.twoSum(new int[] {2, 7, 11, 15}, 9)); - Assert.assertArrayEquals(new int[] {1, 3}, TwoSum2InputArrayIsSorted.twoSum(new int[] {2, 3, 4}, 6)); - Assert.assertArrayEquals(new int[] {1, 2}, TwoSum2InputArrayIsSorted.twoSum(new int[] {0, 0, 3, 4}, 0)); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/AddBinaryTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/str/AddBinaryTest.java deleted file mode 100644 index 9f93884..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/AddBinaryTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.dunwu.ds.str; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class AddBinaryTest { - @Test - public void test() { - Assert.assertEquals("100", AddBinary.addBinary("11", "1")); - Assert.assertEquals("10101", AddBinary.addBinary("1010", "1011")); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ImplementStrstrTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ImplementStrstrTest.java deleted file mode 100644 index 3bd63a1..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ImplementStrstrTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.dunwu.ds.str; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class ImplementStrstrTest { - @Test - public void test() { - Assert.assertEquals(0, ImplementStrstr.strStr("", "")); - Assert.assertEquals(-1, ImplementStrstr.strStr("aaa", "aaaa")); - Assert.assertEquals(0, ImplementStrstr.strStr("aaa", "")); - Assert.assertEquals(2, ImplementStrstr.strStr("hello", "ll")); - Assert.assertEquals(-1, ImplementStrstr.strStr("aaaaa", "bba")); - Assert.assertEquals(1, ImplementStrstr.strStr("mississippi", "issi")); - Assert.assertEquals(9, ImplementStrstr.strStr("mississippi", "pi")); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/LongestCommonPrefixTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/str/LongestCommonPrefixTest.java deleted file mode 100644 index 84149ea..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/LongestCommonPrefixTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.dunwu.ds.str; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class LongestCommonPrefixTest { - @Test - public void test() { - String[] strs1 = {"flower", "flow", "flight"}; - String[] strs2 = {"dog", "racecar", "car"}; - - Assert.assertEquals("fl", LongestCommonPrefix.longestCommonPrefix(strs1)); - Assert.assertEquals("", LongestCommonPrefix.longestCommonPrefix(strs2)); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ReverseStringTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ReverseStringTest.java deleted file mode 100644 index 1855ef6..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ReverseStringTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.dunwu.ds.str; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class ReverseStringTest { - @Test - public void test() { - Assert.assertEquals("olleh", ReverseString.reverseString("hello")); - Assert.assertEquals("amanaP :lanac a ,nalp a ,nam A", - ReverseString.reverseString("A man, a plan, a canal: Panama")); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ReverseWordsInAString3Test.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ReverseWordsInAString3Test.java deleted file mode 100644 index 96cbff1..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ReverseWordsInAString3Test.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.dunwu.ds.str; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class ReverseWordsInAString3Test { - @Test - public void test() { - Assert.assertEquals("s'teL ekat edoCteeL tsetnoc", - ReverseWordsInAString3.reverseWords("Let's take LeetCode contest")); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ReverseWordsInAStringTest.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ReverseWordsInAStringTest.java deleted file mode 100644 index 65892a0..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/str/ReverseWordsInAStringTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.dunwu.ds.str; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Zhang Peng - * @date 2018-11-05 - */ -public class ReverseWordsInAStringTest { - @Test - public void test() { - Assert.assertEquals("blue is sky the", ReverseWordsInAString.reverseWords("the sky is blue")); - Assert.assertEquals(" ", ReverseWordsInAString.reverseWords(" ")); - Assert.assertEquals("1", ReverseWordsInAString.reverseWords("1 ")); - } -} diff --git a/codes/data-structure/src/test/java/io/github/dunwu/ds/tree/BinaryTreeTests.java b/codes/data-structure/src/test/java/io/github/dunwu/ds/tree/BinaryTreeTests.java deleted file mode 100644 index 65f665f..0000000 --- a/codes/data-structure/src/test/java/io/github/dunwu/ds/tree/BinaryTreeTests.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.dunwu.ds.tree; - -import io.github.dunwu.ds.common.JavaCollectionTest; -import io.github.dunwu.ds.common.TreeTest; -import io.github.dunwu.ds.common.Utils; -import org.junit.Test; - -import java.util.Collection; - -import static org.junit.Assert.assertTrue; - - -public class BinaryTreeTests { - - @Test - public void testBTree() { - Utils.TestData data = Utils.generateTestData(1000); - - String bstName = "B-Tree"; - BinaryTree bst = new BinaryTree(2); - Collection bstCollection = bst.toCollection(); - - assertTrue(TreeTest.testTree(bst, Integer.class, bstName, data.unsorted, data.invalid)); - assertTrue(JavaCollectionTest - .testCollection(bstCollection, Integer.class, bstName, data.unsorted, data.sorted, data.invalid)); - } -} diff --git a/codes/pom.xml b/codes/pom.xml deleted file mode 100644 index bc6161c..0000000 --- a/codes/pom.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - 4.0.0 - - io.github.dunwu.algorithm - algorithm - 1.0.1 - pom - Algorithm - 数据结构 - - - algorithm - data-structure - - - https://github.com/dunwu/algorithm - 2016-2018 - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0.txt - repo - A business-friendly OSS license - - - - - Zhang Peng - forbreak@163.com - +8 - - - - Github - https://github.com/dunwu/algorithm/issues - - - https://github.com/dunwu/algorithm - scm:git:git://github.com/dunwu/algorithm.git - scm:git:ssh://git@github.com:dunwu/algorithm.git - - diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js new file mode 100644 index 0000000..f18b41b --- /dev/null +++ b/docs/.vuepress/config.js @@ -0,0 +1,172 @@ +const htmlModules = require('./config/htmlModules.js') + +module.exports = { + port: '4000', + dest: 'docs/.temp', + base: '/algorithm-tutorial/', // 默认'/'。如果你想将你的网站部署到如 https://foo.github.io/bar/,那么 base 应该被设置成 "/bar/",(否则页面将失去样式等文件) + title: 'ALGORITHM-TUTORIAL', + description: '☕ algorithm-tutorial 是一个数据结构与算法教程。', + theme: 'vdoing', // 使用依赖包主题 + // theme: require.resolve('../../vdoing'), // 使用本地主题 + head: [ + // 注入到页面 中的标签,格式[tagName, { attrName: attrValue }, innerHTML?] + ['link', { rel: 'icon', href: '/img/favicon.ico' }], //favicons,资源放在public文件夹 + ['meta', { name: 'keywords', content: 'vuepress,theme,blog,vdoing' }], + ['meta', { name: 'theme-color', content: '#11a8cd' }], // 移动浏览器主题颜色 + ], + markdown: { + // lineNumbers: true, + extractHeaders: ['h2', 'h3', 'h4', 'h5', 'h6'], // 提取标题到侧边栏的级别,默认['h2', 'h3'] + externalLinks: { + target: '_blank', + rel: 'noopener noreferrer', + }, + }, + // 主题配置 + themeConfig: { + nav: [], + sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题) + logo: 'https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo.png', // 导航栏logo + repo: 'dunwu/algorithm-tutorial', // 导航栏右侧生成Github链接 + searchMaxSuggestions: 10, // 搜索结果显示最大数 + lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间) + + docsDir: 'docs', // 编辑的文件夹 + editLinks: true, // 编辑链接 + editLinkText: '📝 帮助改善此页面!', + + // 以下配置是Vdoing主题改动的和新增的配置 + sidebar: { mode: 'structuring', collapsable: false }, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页 + + // sidebarOpen: false, // 初始状态是否打开侧边栏,默认true + updateBar: { + // 最近更新栏 + showToArticle: true, // 显示到文章页底部,默认true + // moreArticle: '/archives' // “更多文章”跳转的页面,默认'/archives' + }, + // titleBadge: false, // 文章标题前的图标是否显示,默认true + // titleBadgeIcons: [ // 文章标题前图标的地址,默认主题内置图标 + // '图标地址1', + // '图标地址2' + // ], + // bodyBgImg: [ + // 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175828.jpeg', + // 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175845.jpeg', + // 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175846.jpeg' + // ], // body背景大图,默认无。 单张图片 String || 多张图片 Array, 多张图片时每隔15秒换一张。 + + // categoryText: '随笔', // 碎片化文章(_posts文件夹的文章)预设生成的分类值,默认'随笔' + + // contentBgStyle: 1, + + category: true, // 是否打开分类功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含分类字段 2.页面中显示与分类相关的信息和模块 3.自动生成分类页面(在@pages文件夹)。如关闭,则反之。 + tag: true, // 是否打开标签功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含标签字段 2.页面中显示与标签相关的信息和模块 3.自动生成标签页面(在@pages文件夹)。如关闭,则反之。 + archive: true, // 是否打开归档功能,默认true。 如打开,会做的事情有:1.自动生成归档页面(在@pages文件夹)。如关闭,则反之。 + + author: { + // 文章默认的作者信息,可在md文件中单独配置此信息 String | {name: String, href: String} + name: 'dunwu', // 必需 + href: 'https://github.com/dunwu', // 可选的 + }, + social: { + // 社交图标,显示于博主信息栏和页脚栏 + // iconfontCssFile: '//at.alicdn.com/t/font_1678482_u4nrnp8xp6g.css', // 可选,阿里图标库在线css文件地址,对于主题没有的图标可自由添加 + icons: [ + { + iconClass: 'icon-youjian', + title: '发邮件', + link: 'mailto:forbreak@163.com', + }, + { + iconClass: 'icon-github', + title: 'GitHub', + link: 'https://github.com/dunwu', + }, + ], + }, + footer: { + // 页脚信息 + createYear: 2019, // 博客创建年份 + copyrightInfo: '钝悟(dunwu) | CC-BY-SA-4.0', // 博客版权信息,支持a标签 + }, + htmlModules, + }, + + // 插件 + plugins: [ + [ + require('./plugins/love-me'), + { + // 鼠标点击爱心特效 + color: '#11a8cd', // 爱心颜色,默认随机色 + excludeClassName: 'theme-vdoing-content', // 要排除元素的class, 默认空'' + }, + ], + + ['fulltext-search'], // 全文搜索 + + // ['thirdparty-search', { // 可以添加第三方搜索链接的搜索框(原官方搜索框的参数仍可用) + // thirdparty: [ // 可选,默认 [] + // { + // title: '在GitHub中搜索', + // frontUrl: 'https://github.com/search?q=', // 搜索链接的前面部分 + // behindUrl: '' // 搜索链接的后面部分,可选,默认 '' + // }, + // { + // title: '在npm中搜索', + // frontUrl: 'https://www.npmjs.com/search?q=', + // }, + // { + // title: '在Bing中搜索', + // frontUrl: 'https://cn.bing.com/search?q=' + // } + // ] + // }], + + [ + 'one-click-copy', + { + // 代码块复制按钮 + copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], // String or Array + copyMessage: '复制成功', // default is 'Copy successfully and then paste it for use.' + duration: 1000, // prompt message display time. + showInMobile: false, // whether to display on the mobile side, default: false. + }, + ], + [ + 'demo-block', + { + // demo演示模块 https://github.com/xiguaxigua/vuepress-plugin-demo-block + settings: { + // jsLib: ['http://xxx'], // 在线示例(jsfiddle, codepen)中的js依赖 + // cssLib: ['http://xxx'], // 在线示例中的css依赖 + // vue: 'https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js', // 在线示例中的vue依赖 + jsfiddle: false, // 是否显示 jsfiddle 链接 + codepen: true, // 是否显示 codepen 链接 + horizontal: false, // 是否展示为横向样式 + }, + }, + ], + [ + 'vuepress-plugin-zooming', // 放大图片 + { + selector: '.theme-vdoing-content img:not(.no-zoom)', + options: { + bgColor: 'rgba(0,0,0,0.6)', + }, + }, + ], + [ + '@vuepress/last-updated', // "上次更新"时间格式 + { + transformer: (timestamp, lang) => { + const dayjs = require('dayjs') // https://day.js.org/ + return dayjs(timestamp).format('YYYY/MM/DD, HH:mm:ss') + }, + }, + ], + ], + + // 监听文件变化并重新构建 + extraWatchFiles: ['.vuepress/config.js', '.vuepress/config/htmlModules.js'], +} diff --git a/docs/.vuepress/config/baiduCode.js b/docs/.vuepress/config/baiduCode.js new file mode 100644 index 0000000..9dc5fc1 --- /dev/null +++ b/docs/.vuepress/config/baiduCode.js @@ -0,0 +1 @@ +module.exports = ''; diff --git a/docs/.vuepress/config/htmlModules.js b/docs/.vuepress/config/htmlModules.js new file mode 100644 index 0000000..6ba3782 --- /dev/null +++ b/docs/.vuepress/config/htmlModules.js @@ -0,0 +1,52 @@ +/** 插入自定义html模块 (可用于插入广告模块等) + * { + * homeSidebarB: htmlString, 首页侧边栏底部 + * + * sidebarT: htmlString, 全局左侧边栏顶部 + * sidebarB: htmlString, 全局左侧边栏底部 + * + * pageT: htmlString, 全局页面顶部 + * pageB: htmlString, 全局页面底部 + * pageTshowMode: string, 页面顶部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页① + * pageBshowMode: string, 页面底部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页① + * + * windowLB: htmlString, 全局左下角② + * windowRB: htmlString, 全局右下角② + * } + * + * ①注:在.md文件front matter配置`article: false`的页面是自定义页,未配置的默认是文章页(首页除外)。 + * ②注:windowLB 和 windowRB:1.展示区块最大宽高200px*400px。2.请给自定义元素定一个不超过200px*400px的宽高。3.在屏幕宽度小于960px时无论如何都不会显示。 + */ + +module.exports = { + // 万维广告 + pageB: ` +
+ + `, + windowRB: ` +
+ + `, +} + +// module.exports = { +// homeSidebarB: `
自定义模块测试
`, +// sidebarT: `
自定义模块测试
`, +// sidebarB: `
自定义模块测试
`, +// pageT: `
自定义模块测试
`, +// pageB: `
自定义模块测试
`, +// windowLB: `
自定义模块测试
`, +// windowRB: `
自定义模块测试
`, +// } diff --git a/docs/.vuepress/config/sidebar.js b/docs/.vuepress/config/sidebar.js new file mode 100644 index 0000000..9c97278 --- /dev/null +++ b/docs/.vuepress/config/sidebar.js @@ -0,0 +1,105 @@ +// !!!注:此文件没有使用到,仅用于测试和侧边栏数据格式的参考。 + +// 侧边栏 +module.exports = { + '/01.前端/': [ + { + title: 'JavaScript', + collapsable: false, //是否可折叠,可选的,默认true + children: [ + ['01.JavaScript/01.JavaScript中的名词概念','JavaScript中的名词概念'], + ['01.JavaScript/02.数据类型转换','数据类型转换'], + ['01.JavaScript/03.ES5面向对象','ES5面向对象'], + ['01.JavaScript/04.ES6面向对象','ES6面向对象'], + ['01.JavaScript/05.new命令原理','new命令原理'], + ['01.JavaScript/06.多种数组去重性能对比','多种数组去重性能对比'], + ] + }, + ], + '/02.页面/': [ + { + title: 'html-css', + collapsable: false, + children: [ + ['01.html-css/00.flex布局语法','flex布局语法'], + ['01.html-css/01.flex布局案例-基础','flex布局案例-基础'], + ['01.html-css/02.flex布局案例-骰子','flex布局案例-骰子'], + ['01.html-css/03.flex布局案例-网格布局','flex布局案例-网格布局'], + ['01.html-css/04.flex布局案例-圣杯布局','flex布局案例-圣杯布局'], + ['01.html-css/05.flex布局案例-输入框布局','flex布局案例-输入框布局'], + ['01.html-css/06.CSS3之transform过渡','CSS3之transform过渡'], + ['01.html-css/07.CSS3之animation动画','CSS3之animation动画'], + ] + }, + ], + '/03.技术杂谈/': [ + { + title: '技术杂谈', + collapsable: false, //是否可折叠,可选的,默认true + sidebarDepth: 2, // 深度,可选的, 默认值是 1 + children: [ + ['01.Git使用手册','Git使用手册'], // 同 {path: '01.Git使用手册', title: 'Git使用文档'} + ['02.GitHub高级搜索技巧','GitHub高级搜索技巧'], + ['03.Markdown使用教程','Markdown使用教程'], + ['04.npm常用命令','npm常用命令'], + ['05.yaml语言教程','yaml语言教程'], + ['06.解决百度无法收录搭建在GitHub上的个人博客的问题','解决百度无法收录搭建在GitHub上的个人博客的问题'], + ['07.使用Gitalk实现静态博客无后台评论系统','使用Gitalk实现静态博客无后台评论系统'], + ] + } + ], + '/04.其他/': [ + { + title: '学习', + collapsable: false, + children: [ + ['01.学习/01.学习网站','学习网站'], + ['01.学习/02.学习效率低,忘性很大怎么办?','学习效率低,忘性很大怎么办?'], + ] + }, + { + title: '学习笔记', + collapsable: false, + children: [ + ['02.学习笔记/01.小程序笔记','小程序笔记'], + ] + }, + { + title: '面试', + collapsable: false, //是否可折叠,可选的,默认true + children: [ + ['03.面试/01.面试问题集锦','面试问题集锦'], + ] + }, + ['01.在线工具','在线工具'], + ['02.友情链接','友情链接'], + ], + // '/': [ // 在最后定义,在没有单独设置侧边栏时统一使用这个侧边栏 + // '', + // 'git', + // 'github', + // 'markdown', + // 'study', + // 'interview' + // // '/', + // // { + // // title: 'foo', // 标题,必要的 + // // path: '/foo/', // 标题的路径,可选的, 应该是一个绝对路径 + // // collapsable: false, // 是否可折叠,可选的,默认true + // // sidebarDepth: 1, // 深度,可选的, 默认值是 1 + // // children: [ + // // ['foo/', '子页1'], + // // 'foo/1', + // // 'foo/2', + // // ] + // // }, + // // { + // // title: 'bar', + // // children: [ + // // ['bar/', '子页2'], + // // 'bar/3', + // // 'bar/4', + // // ] + // // } + // ], +} diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js new file mode 100644 index 0000000..6dd9f98 --- /dev/null +++ b/docs/.vuepress/enhanceApp.js @@ -0,0 +1,64 @@ +// import vue from 'vue/dist/vue.esm.browser' +export default ({ + Vue, // VuePress 正在使用的 Vue 构造函数 + options, // 附加到根实例的一些选项 + router, // 当前应用的路由实例 + siteData // 站点元数据 +}) => { + try { + document && integrateGitalk(router) + } catch (e) { + console.error(e.message) + } +} + +// 集成 Gitalk 评论插件 +function integrateGitalk(router) { + const linkGitalk = document.createElement('link') + linkGitalk.href = 'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css' + linkGitalk.rel = 'stylesheet' + document.body.appendChild(linkGitalk) + const scriptGitalk = document.createElement('script') + scriptGitalk.src = 'https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js' + document.body.appendChild(scriptGitalk) + + router.afterEach((to) => { + if (scriptGitalk.onload) { + loadGitalk(to) + } else { + scriptGitalk.onload = () => { + loadGitalk(to) + } + } + }) + + function loadGitalk(to) { + let commentsContainer = document.getElementById('gitalk-container') + if (!commentsContainer) { + commentsContainer = document.createElement('div') + commentsContainer.id = 'gitalk-container' + commentsContainer.classList.add('content') + } + const $page = document.querySelector('.page') + if ($page) { + $page.appendChild(commentsContainer) + if (typeof Gitalk !== 'undefined' && Gitalk instanceof Function) { + renderGitalk(to.fullPath) + } + } + } + function renderGitalk(fullPath) { + console.info(fullPath) + const gitalk = new Gitalk({ + clientID: '8772d9c11ed3dc0b8922', + clientSecret: '7c6d2d583ff9437f5405bf9479e08db63d3a75fb', // come from github development + repo: 'blog', + owner: 'dunwu', + admin: ['dunwu'], + id: 'comment', + distractionFreeMode: false, + language: 'zh-CN', + }) + gitalk.render('gitalk-container') + } +} diff --git a/docs/.vuepress/plugins/love-me/index.js b/docs/.vuepress/plugins/love-me/index.js new file mode 100644 index 0000000..67f5ea9 --- /dev/null +++ b/docs/.vuepress/plugins/love-me/index.js @@ -0,0 +1,12 @@ +const path = require('path') +const LoveMyPlugin = (options = {}) => ({ + define() { + const COLOR = + options.color || + 'rgb(' + ~~(255 * Math.random()) + ',' + ~~(255 * Math.random()) + ',' + ~~(255 * Math.random()) + ')' + const EXCLUDECLASS = options.excludeClassName || '' + return { COLOR, EXCLUDECLASS } + }, + enhanceAppFiles: [path.resolve(__dirname, 'love-me.js')], +}) +module.exports = LoveMyPlugin diff --git a/docs/.vuepress/plugins/love-me/love-me.js b/docs/.vuepress/plugins/love-me/love-me.js new file mode 100644 index 0000000..f93855e --- /dev/null +++ b/docs/.vuepress/plugins/love-me/love-me.js @@ -0,0 +1,62 @@ +export default () => { + if (typeof window !== "undefined") { + (function(e, t, a) { + function r() { + for (var e = 0; e < s.length; e++) s[e].alpha <= 0 ? (t.body.removeChild(s[e].el), s.splice(e, 1)) : (s[e].y--, s[e].scale += .004, s[e].alpha -= .013, s[e].el.style.cssText = "left:" + s[e].x + "px;top:" + s[e].y + "px;opacity:" + s[e].alpha + ";transform:scale(" + s[e].scale + "," + s[e].scale + ") rotate(45deg);background:" + s[e].color + ";z-index:99999"); + requestAnimationFrame(r) + } + function n() { + var t = "function" == typeof e.onclick && e.onclick; + + e.onclick = function(e) { + // 过滤指定元素 + let mark = true; + EXCLUDECLASS && e.path && e.path.forEach((item) =>{ + if(item.nodeType === 1) { + typeof item.className === 'string' && item.className.indexOf(EXCLUDECLASS) > -1 ? mark = false : '' + } + }) + + if(mark) { + t && t(), + o(e) + } + } + } + function o(e) { + var a = t.createElement("div"); + a.className = "heart", + s.push({ + el: a, + x: e.clientX - 5, + y: e.clientY - 5, + scale: 1, + alpha: 1, + color: COLOR + }), + t.body.appendChild(a) + } + function i(e) { + var a = t.createElement("style"); + a.type = "text/css"; + try { + a.appendChild(t.createTextNode(e)) + } catch(t) { + a.styleSheet.cssText = e + } + t.getElementsByTagName("head")[0].appendChild(a) + } + // function c() { + // return "rgb(" + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + ")" + // } + var s = []; + e.requestAnimationFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame || + function(e) { + setTimeout(e, 1e3 / 60) + }, + i(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"), + n(), + r() + })(window, document) + } +} \ No newline at end of file diff --git a/docs/.vuepress/public/favicon.ico b/docs/.vuepress/public/favicon.ico new file mode 100644 index 0000000..51e9bfa Binary files /dev/null and b/docs/.vuepress/public/favicon.ico differ diff --git a/docs/.vuepress/public/img/bg.gif b/docs/.vuepress/public/img/bg.gif new file mode 100644 index 0000000..d4bf3c4 Binary files /dev/null and b/docs/.vuepress/public/img/bg.gif differ diff --git a/docs/.vuepress/public/img/bg.jpeg b/docs/.vuepress/public/img/bg.jpeg new file mode 100644 index 0000000..85e53e7 Binary files /dev/null and b/docs/.vuepress/public/img/bg.jpeg differ diff --git a/docs/.vuepress/public/img/bg.jpg b/docs/.vuepress/public/img/bg.jpg new file mode 100644 index 0000000..f093e79 Binary files /dev/null and b/docs/.vuepress/public/img/bg.jpg differ diff --git a/docs/.vuepress/public/img/dunwu-logo.png b/docs/.vuepress/public/img/dunwu-logo.png new file mode 100644 index 0000000..61570e2 Binary files /dev/null and b/docs/.vuepress/public/img/dunwu-logo.png differ diff --git a/docs/.vuepress/public/img/favicon.ico b/docs/.vuepress/public/img/favicon.ico new file mode 100644 index 0000000..51e9bfa Binary files /dev/null and b/docs/.vuepress/public/img/favicon.ico differ diff --git a/docs/.vuepress/public/img/git.png b/docs/.vuepress/public/img/git.png new file mode 100644 index 0000000..82ba43f Binary files /dev/null and b/docs/.vuepress/public/img/git.png differ diff --git a/docs/.vuepress/public/img/logo.png b/docs/.vuepress/public/img/logo.png new file mode 100644 index 0000000..8e1d567 Binary files /dev/null and b/docs/.vuepress/public/img/logo.png differ diff --git a/docs/.vuepress/public/img/more.png b/docs/.vuepress/public/img/more.png new file mode 100644 index 0000000..830613b Binary files /dev/null and b/docs/.vuepress/public/img/more.png differ diff --git a/docs/.vuepress/public/img/other.png b/docs/.vuepress/public/img/other.png new file mode 100644 index 0000000..87f8098 Binary files /dev/null and b/docs/.vuepress/public/img/other.png differ diff --git a/docs/.vuepress/public/img/panda-waving.png b/docs/.vuepress/public/img/panda-waving.png new file mode 100644 index 0000000..20246c6 Binary files /dev/null and b/docs/.vuepress/public/img/panda-waving.png differ diff --git a/docs/.vuepress/public/img/python.png b/docs/.vuepress/public/img/python.png new file mode 100644 index 0000000..c3ddebe Binary files /dev/null and b/docs/.vuepress/public/img/python.png differ diff --git a/docs/.vuepress/public/img/ui.png b/docs/.vuepress/public/img/ui.png new file mode 100644 index 0000000..617c56d Binary files /dev/null and b/docs/.vuepress/public/img/ui.png differ diff --git a/docs/.vuepress/public/img/web.png b/docs/.vuepress/public/img/web.png new file mode 100644 index 0000000..0a6e27c Binary files /dev/null and b/docs/.vuepress/public/img/web.png differ diff --git a/docs/.vuepress/public/markmap/01.html b/docs/.vuepress/public/markmap/01.html new file mode 100644 index 0000000..c55f2d0 --- /dev/null +++ b/docs/.vuepress/public/markmap/01.html @@ -0,0 +1,25 @@ + + + + + + +Markmap + + + + + + + + diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl new file mode 100644 index 0000000..3113dd6 --- /dev/null +++ b/docs/.vuepress/styles/index.styl @@ -0,0 +1,93 @@ +.home-wrapper .banner .banner-conent .hero h1{ + font-size 2.8rem!important +} +// 文档中适配 +table + width auto +.page >*:not(.footer),.card-box + box-shadow: none!important + +.page + @media (min-width $contentWidth + 80) + padding-top $navbarHeight!important +.home-wrapper .banner .banner-conent + padding 0 2.9rem + box-sizing border-box +.home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a + h2 + margin-top 2rem + font-size 1.2rem!important + p + padding 0 1rem + +// 评论区颜色重置 +.gt-container + .gt-ico-tip + &::after + content: '。( Win + . ) or ( ⌃ + ⌘ + ␣ ) open Emoji' + color: #999 + .gt-meta + border-color var(--borderColor)!important + .gt-comments-null + color var(--textColor) + opacity .5 + .gt-header-textarea + color var(--textColor) + background rgba(180,180,180,0.1)!important + .gt-btn + border-color $accentColor!important + background-color $accentColor!important + .gt-btn-preview + background-color rgba(255,255,255,0)!important + color $accentColor!important + a + color $accentColor!important + .gt-svg svg + fill $accentColor!important + .gt-comment-content,.gt-comment-admin .gt-comment-content + background-color rgba(150,150,150,0.1)!important + &:hover + box-shadow 0 0 25px rgba(150,150,150,.5)!important + .gt-comment-body + color var(--textColor)!important + + +// qq徽章 +.qq + position: relative; +.qq::after + content: "可撩"; + background: $accentColor; + color:#fff; + padding: 0 5px; + border-radius: 10px; + font-size:12px; + position: absolute; + top: -4px; + right: -35px; + transform:scale(0.85); + +// demo模块图标颜色 +body .vuepress-plugin-demo-block__wrapper + &,.vuepress-plugin-demo-block__display + border-color rgba(160,160,160,.3) + .vuepress-plugin-demo-block__footer:hover + .vuepress-plugin-demo-block__expand::before + border-top-color: $accentColor !important; + border-bottom-color: $accentColor !important; + svg + fill: $accentColor !important; + + +// 全文搜索框 +.suggestions + overflow: auto + max-height: calc(100vh - 6rem) + @media (max-width: 719px) { + width: 90vw; + min-width: 90vw!important; + margin-right: -20px; + } + .highlight + color: $accentColor + font-weight: bold diff --git a/docs/.vuepress/styles/palette.styl b/docs/.vuepress/styles/palette.styl new file mode 100644 index 0000000..d98e697 --- /dev/null +++ b/docs/.vuepress/styles/palette.styl @@ -0,0 +1,62 @@ + +// 原主题变量已弃用,以下是vdoing使用的变量,你可以在这个文件内修改它们。 + +//***vdoing主题-变量***// + +// // 颜色 + +// $bannerTextColor = #fff // 首页banner区(博客标题)文本颜色 +// $accentColor = #11A8CD +// $arrowBgColor = #ccc +// $badgeTipColor = #42b983 +// $badgeWarningColor = darken(#ffe564, 35%) +// $badgeErrorColor = #DA5961 + +// // 布局 +// $navbarHeight = 3.6rem +// $sidebarWidth = 18rem +// $contentWidth = 860px +// $homePageWidth = 1100px +// $rightMenuWidth = 230px // 右侧菜单 + +// // 代码块 +// $lineNumbersWrapperWidth = 2.5rem + +// 浅色模式 +.theme-mode-light + --bodyBg: rgba(255,255,255,1) + --mainBg: rgba(255,255,255,1) + --sidebarBg: rgba(255,255,255,.8) + --blurBg: rgba(255,255,255,.9) + --textColor: #004050 + --textLightenColor: #0085AD + --borderColor: rgba(0,0,0,.15) + --codeBg: #f6f6f6 + --codeColor: #525252 + codeThemeLight() + +// 深色模式 +.theme-mode-dark + --bodyBg: rgba(30,30,34,1) + --mainBg: rgba(30,30,34,1) + --sidebarBg: rgba(30,30,34,.8) + --blurBg: rgba(30,30,34,.8) + --textColor: rgb(140,140,150) + --textLightenColor: #0085AD + --borderColor: #2C2C3A + --codeBg: #252526 + --codeColor: #fff + codeThemeDark() + +// 阅读模式 +.theme-mode-read + --bodyBg: rgba(245,245,213,1) + --mainBg: rgba(245,245,213,1) + --sidebarBg: rgba(245,245,213,.8) + --blurBg: rgba(245,245,213,.9) + --textColor: #004050 + --textLightenColor: #0085AD + --borderColor: rgba(0,0,0,.15) + --codeBg: #282c34 + --codeColor: #fff + codeThemeDark() diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" new file mode 100644 index 0000000..7e0fa0b --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" @@ -0,0 +1,33 @@ +--- +title: 数据结构和算法指南 +categories: + - 数据结构和算法 + - 综合 +tags: + - 数据结构 + - 算法 +abbrlink: e74901af +date: 2015-03-10 18:29:37 +permalink: /pages/8b1bd0/ +--- +# 数据结构和算法指南 + +## 1. 为什么学习数据结构和算法 + +- **为了找到一份好工作**:大厂面试喜欢考算法 +- **更深入了解流行技术的设计思想**:数据结构和算法是计算机基础学科,很多框架、中间、底层系统设的设计,都借鉴了其思想。因此,掌握数据结构和算法,有利于更深入了解这些技术的设计思想。 +- 提升个人的编程水平 +- 不满足于做业务狗,拓展性能思考的视角 + +## 2. 如何学习数据结构和算法 + +数据结构就是指一组数据的存储结构。算法就是操作数据的一组方法。 + +数据结构和算法是相辅相成的。**数据结构是为算法服务的,算法要作用在特定的数据结构之上。** + +先要学会复杂度分析,才能识别数据结构和算法的利弊。 + +- 循序渐进 +- 边学边练,适度刷题 +- 学习并思考:学而不思则罔,思而不学则殆 +- 知识需要沉淀,不要想试图一下子掌握所有 diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" new file mode 100644 index 0000000..8ff51fc --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" @@ -0,0 +1,171 @@ +--- +title: 复杂度分析 +categories: + - 数据结构和算法 + - 综合 +tags: + - 数据结构 + - 算法 +abbrlink: a1a87ec3 +date: 2022-03-20 23:25:17 +permalink: /pages/2a4131/ +--- + +# 复杂度分析 + +## 为什么需要复杂度分析 + +衡量算法的优劣,有两种评估方式:事前估计和后期测试。 + +后期测试有性能测试、基准测试(Benchmark)等手段。 + +但是,后期测试有以下限制: + +- **测试结果非常依赖测试环境**。如:不同机型、不同编译器版本、不同硬件配置等等,都会影响测试结果。 +- **测试结果受数据规模的影响很大**。 + +所以,需要一种方法,可以不受环境或数据规模的影响,粗略地估计算法的执行效率。这种方法就是复杂度分析。 + +## 时间复杂度分析 + +### 大 O 表示法 + +假设问题的规模为 n,则程序的时间复杂度表示为 `T(n)`。**代码的执行时间 T(n) 与每行代码的执行次数 n 成正比**。 + +当 n 增大时,T(n) 也随之增大,想要准确估计其变化比较困难。所以,可以采用大 O 时间复杂度来粗略估计其复杂度,其表达式为:**`T(n) = O(f(n))`**。 + +**大 O 表示法**实际上并不具体表示代码真正的执行时间,而是表示**代码执行时间随数据规模增长的变化趋势**,所以,也叫作**渐进时间复杂度**(asymptotic time complexity),简称**时间复杂度**。 + +### 时间复杂度分析的要点 + +- **只关注循环执行次数最多的一段代码** +- **加法法则:总复杂度等于量级最大的那段代码的复杂度** +- **乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积** + +### 最好、最坏和平均情况 + +- **最好情况时间复杂度**(best case time complexity):**在最理想的情况下,执行代码的时间复杂度**。例如:在最理想的情况下,要查找的变量 x 正好是数组的第一个元素,此时最好情况时间复杂度为 1。 +- **最坏情况时间复杂度**(worst case time complexity):**在最糟糕的情况下,执行代码的时间复杂度**。例如:在最理想的情况下,要查找的变量 x 正好是数组的最后个元素,此时最好情况时间复杂度为 n。 +- **平均情况时间复杂度**(average case time complexity):平均时间复杂度的全称应该叫**加权平均时间复杂度**或者**期望时间复杂度**。 + +### 时间复杂度分析示例 + +【示例】从 1 累加到 100 的时间复杂度是多少? + +```java +int sum = 0; +int N = 100; +for (int i = 1; i <= N; i++) { + sum = sum + i; +} +``` + +时间复杂度计算:显然,这段代码执行了 100 次加法,其时间复杂度和 N 的大小完全一致 + +``` +T(n) = O(n) +``` + +【示例】嵌套循环的时间复杂度是多少? + +```java +int M = 10; +int N = 20; +for (int i = 1; i < M; i++) { + for (int j = 1; j < N; j++) { + System.out.println("i = " + i + ", j = " + j); + } +} +``` + +时间复杂度计算: + +``` +T(n) = (M-1)(N-1) = O(M*N) ≈ O(N^2) +``` + +【示例】递归函数的时间复杂度是多少?思考一下斐波那契数列 `f(n) = f(n-1) + f(n-2)` 的时间复杂度是多少? + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320110642.png) + +``` +T(n) = O(2^N) +``` + +## 空间复杂度分析 + +时间复杂度的全称是**渐进时间复杂度**,**表示算法的执行时间与数据规模之间的增长关系**。 + +类比一下,空间复杂度全称就是**渐进空间复杂度**(asymptotic space complexity),**表示算法的存储空间与数据规模之间的增长关系**。 + +## 复杂度量级 + +复杂度有以下量级: + +- **`O(1)`**:常数复杂度 +- **`O(log n)`**:对数复杂度 +- **`O(n)`**:线性复杂度 +- **`O(nlog n)`**:线性对数阶复杂度 +- **`O(n^2)`**:平方复杂度 +- **`O(n^3)`**:立方复杂度 +- **`O(n^k)`**:K 次方复杂度 +- **`O(2^n)`**:指数复杂度 +- **`O(n!)`**:阶乘复杂度 + +在数据量比较小的时候,复杂度量级差异并不明显;但是,随着数据规模大小的变化,差异会逐渐突出。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320160627.png) + +`O(1)` 复杂度示例: + +```java +int num = 100; +System.out.println("num = " + num); +``` + +`O(log n)` 对数复杂度示例: + +```java +int max = 100; +for (int i = 1; i < max; i = i * 2) { + System.out.println("i = " + i); +} +``` + +`O(n)` 复杂度示例: + +```java +int max = 100; +for (int i = 1; i < max; i++) { + System.out.println("i = " + i); +} +``` + +`O(n^2)` 复杂度示例: + +```java +int M = 10; +int N = 20; +for (int i = 1; i < M; i++) { + for (int j = 1; j < N; j++) { + System.out.println("i = " + i + ", j = " + j); + } +} +``` + +`O(k^n)` 复杂度示例: + +```java +int max = 10; +for (int i = 1; i <= Math.pow(2, max); i++) { + System.out.println("i = " + i); +} +``` + +## 常见数据结构的复杂度 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200702071922.png) + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" new file mode 100644 index 0000000..343b216 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" @@ -0,0 +1,440 @@ +--- +title: 数组和链表 +categories: + - 数据结构和算法 + - 线性表 +tags: + - 数据结构 + - 线性表 + - 数组 + - 链表 +abbrlink: 50ba53aa +date: 2015-04-10 18:46:13 +permalink: /pages/5a9bff/ +--- + +# 数组和链表 + +> 数组和链表分别代表了连续空间和不连续空间的存储方式,它们是线性表(Linear List)的典型代表。其他所有的数据结构,比如栈、队列、二叉树、B+ 树等,实际上都是这两者的结合和变化。 + +## 数组 + +数组用 **连续** 的内存空间来存储数据。 + +### 数组的访问 + +数组元素的访问是以行或列索引的单一下标表示。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115836.png) + +在上面的例子中,数组 a 中有 5 个元素。`也就是说`,a 的长度是 6 。我们可以使用 a[0] 来表示数组中的第一个元素。因此,a[0] = A 。类似地,a[1] = B,a[2] = C,依此类推。 + +### 数组的插入 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115848.png) + +### 数组的删除 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115859.png) + +### 数组的特性 + +数组设计之初是在形式上依赖内存分配而成的,所以必须在使用前预先分配好空间大小。这使得数组有以下特性: + +1. **用连续的内存空间来存储数据**。 +2. **数组支持随机访问,根据下标随机访问的时间复杂度为 `O(1)`**。 +3. **数组的插入、删除操作,平均时间复杂度为 `O(n)`**。 +4. **空间大小固定**,一旦建立,不能再改变。扩容只能采用复制数组的方式。 +5. 在旧式编程语言中(如有中阶语言之称的 C),程序不会对数组的操作做下界判断,也就有潜在的越界操作的风险。 + +### 多维数组 + +数组是有下标和值组成集合。 + +如果数组的下标有多个维度,即为多维数组。比如:二维数组可以视为『数组元素为一维数组』的一维数组;三维数组可以视为『数组元素为二维数组』的一维数组;依次类推。 + +下图是由 M 个行向量,N 个列向量组成的二维数组. + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320152607.png) + +## 链表 + +> **链表用不连续的内存空间来存储数据;并通过一个指针按顺序将这些空间串起来,形成一条链**。 + +区别于数组,链表中的元素不是存储在内存中连续的一片区域,链表中的数据存储在每一个称之为「结点」复合区域里,在每一个结点除了存储数据以外,还保存了到下一个节点的指针(Pointer)。由于不必按顺序存储,链表在插入数据的时候可以达到 `O(1)` 的复杂度,但是查找一个节点或者访问特定编号的节点则需要 `O(n)` 的时间。 + +链表具有以下特性: + +- 链表允许插入和移除任意位置上的节点,其时间复杂度为 `O(1)` +- 链表没有数组的随机访问特性,**链表只支持顺序访问**,其时间复杂度为 `O(n)`。 +- 数组的空间大小是固定的,而**链表的空间大小可以动态增长**。相比于数组,链表支持扩容,显然更为灵活,但是由于多了指针域,空间开销也更大。 +- 链表相比于数组,多了头指针、尾指针(非必要),合理使用可以大大提高访问效率。 + +链表有多种类型: + +- 单链表 +- 双链表 +- 循环链表 + +### 单链表 + +单链表中的每个结点不仅包含数据值,还包含一个指针,指向其后继节点。通过这种方式,单链表将所有结点按顺序组织起来。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174829.png) + +与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按 `索引` 来 `访问元素` 平均要花费 `O(N)` 时间,其中 N 是链表的长度。 + +#### 单链表插入 + +如果我们想在给定的结点 `prev` 之后添加新值,我们应该: + +(1)使用给定值初始化新结点 `cur`; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174908.png) + +(2)将 `cur` 的 `next` 字段链接到 `prev` 的下一个结点 `next` ; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174919.png) + +(3)将 `prev` 中的 `next` 字段链接到 `cur` 。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174932.png) + +与数组不同,我们不需要将所有元素移动到插入元素之后。因此,您可以在 `O(1)` 时间复杂度中将新结点插入到链表中,这非常高效。 + +#### 单链表删除 + +如果我们想从单链表中删除现有结点 `cur`,可以分两步完成: + +(1)找到 `cur` 的上一个结点 `prev` 及其下一个结点 `next` ; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174953.png) + +(2)接下来链接 `prev` 到 `cur` 的下一个节点 `next` 。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320175006.png) + +在我们的第一步中,我们需要找出 `prev` 和 `next`。使用 `cur` 的参考字段很容易找出 `next`,但是,我们必须从头结点遍历链表,以找出 `prev`,它的平均时间是 `O(N)`,其中 `N` 是链表的长度。因此,删除结点的时间复杂度将是 `O(N)`。 + +空间复杂度为 `O(1)`,因为我们只需要常量空间来存储指针。 + +### 双链表 + +双链表中的每个结点不仅包含数据值,还包含两个指针,分别指向指向其前驱节点和后继节点。 + +单链表的访问是单向的,而双链表的访问是双向的。显然,双链表比单链表操作更灵活,但是空间开销也更大。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181150.png) + +双链表以类似的方式工作,但`还有一个引用字段`,称为`“prev”`字段。有了这个额外的字段,您就能够知道当前结点的前一个结点。 + +#### 双链表插入 + +如果我们想在给定的结点 `prev` 之后添加新值,我们应该: + +(1)使用给定值初始化新结点 `cur`; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181208.png) + +(2)链接 `cur` 与 `prev` 和 `next`,其中 `next` 是 `prev` 原始的下一个节点; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181303.png) + +(3)用 `cur` 重新链接 `prev` 和 `next`。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181504.png) + +与单链表类似,添加操作的时间和空间复杂度都是 `O(1)`。 + +#### 双链表删除 + +如果我们想从双链表中删除一个现有的结点 `cur`,我们可以简单地将它的前一个结点 `prev` 与下一个结点 `next` 链接起来。 + +与单链表不同,使用 `prev` 字段可以很容易地在常量时间内获得前一个结点。 + +因为我们不再需要遍历链表来获取前一个结点,所以时间和空间复杂度都是 `O(1)`。 + +### 循环链表 + +#### 循环单链表 + +**循环单链表是一种特殊的单链表**。它和单链表唯一的区别就在最后结点。 + +- 单链表的最后一个结点的后继指针 `next` 指向空地址。 +- 循环链表的最后一个结点的后继指针 `next` 指向第一个节点(如果有头节点,就指向头节点)。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220322190534.png) + +#### 循环双链表 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220322190423.png) + +## 数组 vs. 链表 + +- **存储方式** + - 数组用 **连续** 的内存空间来存储数据。 + - 链表用 **不连续** 的内存空间来存储数据;并通过一个指针按顺序将这些空间串起来,形成一条链。 +- **访问方式** + - 数组**支持随机访问**。根据下标随机访问的时间复杂度为 `O(1)` + - 链表**不支持随机访问**,只能顺序访问,时间复杂度为 `O(n)`。 +- **空间大小** + - 数组空间**大小固定**,扩容只能采用复制数组的方式。 + - 链表空间**大小不固定**,扩容灵活。 +- **效率比较** + - 数组的 **查找** 效率高于链表。 + - 链表的 **添加**、**删除** 效率高于数组。 + +## 数组和链表的基本操作示例 + +关于数组和链表的基本操作,网上和各种书籍、教程中已经有大量的示例,感兴趣可以自行搜索。本文只是简单展示一下数组和链表的基本操作。 + +### 一维数组的基本操作 + +```java +public class Main { + public static void main(String[] args) { + // 1. Initialize + int[] a0 = new int[5]; + int[] a1 = {1, 2, 3}; + // 2. Get Length + System.out.println("The size of a1 is: " + a1.length); + // 3. Access Element + System.out.println("The first element is: " + a1[0]); + // 4. Iterate all Elements + System.out.print("[Version 1] The contents of a1 are:"); + for (int i = 0; i < a1.length; ++i) { + System.out.print(" " + a1[i]); + } + System.out.println(); + System.out.print("[Version 2] The contents of a1 are:"); + for (int item: a1) { + System.out.print(" " + item); + } + System.out.println(); + // 5. Modify Element + a1[0] = 4; + // 6. Sort + Arrays.sort(a1); + } +} +``` + +### 二维数组的基本操作 + +```java +public class TwoDimensionArray { + private static void printArray(int[][] a) { + for (int i = 0; i < a.length; ++i) { + System.out.println(a[i]); + } + for (int i = 0; i < a.length; ++i) { + for (int j = 0; a[i] != null && j < a[i].length; ++j) { + System.out.print(a[i][j] + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + System.out.println("Example I:"); + int[][] a = new int[2][5]; + printArray(a); + System.out.println("Example II:"); + int[][] b = new int[2][]; + printArray(b); + System.out.println("Example III:"); + b[0] = new int[3]; + b[1] = new int[5]; + printArray(b); + } +} +``` + +### 单链表的基本操作 + +单链表节点的数据结构 + +```java +public class ListNode { + E value; + ListNode next; // 指向后继节点 +} + +public class SingleLinkList { + private ListNode head; // 头节点 +} +``` + +(1)从头部添加节点(即头插法) + +```java +void addHead(E value) { + ListNode newNode = new ListNode<>(value, null); + newNode.next = this.head.next; + this.head.next = newNode; +} +``` + +(2)从尾部添加节点(即尾插法) + +```java +void addTail(E value) { + // init new node + ListNode newNode = new ListNode<>(value, null); + + // find the last node + ListNode node = this.head; + while (node.next != null) { + node = node.next; + } + + // add new node to tail + node.next = newNode; +} +``` + +(3)删除节点 + +找到要删除元素的前驱节点,将前驱节点的 next 指针指向下一个节点。 + +```java +public void remove(E value) { + ListNode prev = this.head; + while (prev.next != null) { + ListNode curr = prev.next; + if (curr.value.equals(value)) { + prev.next = curr.next; + break; + } + prev = prev.next; + } +} +``` + +(4)查找节点 + +从头开始查找,一旦发现有数值与查找值相等的节点,直接返回此节点。如果遍历结束,表明未找到节点,返回 null。 + +```java +public ListNode find(E value) { + ListNode node = this.head.next; + while (node != null) { + if (node.value.equals(value)) { + return node; + } + node = node.next; + } + return null; +} +``` + +### 双链表的基本操作 + +双链表节点的数据结构: + +```java +static class DListNode { + E value; + DListNode prev; // 指向前驱节点 + DListNode next; // 指向后继节点 +} + +public class DoubleLinkList { + /** 头节点 */ + private DListNode head; + /** 尾节点 */ + private DListNode tail; +} +``` + +(1)从头部添加节点 + +```java +public void addHead(E value) { + DListNode newNode = new DListNode<>(null, value, null); + + this.head.next.prev = newNode; + newNode.next = this.head.next; + + this.head.next = newNode; + newNode.prev = this.head; +} +``` + +(2)从尾部添加节点 + +```java +public void addTail(E value) { + DListNode newNode = new DListNode<>(null, value, null); + + this.tail.prev.next = newNode; + newNode.prev = this.tail.prev; + + this.tail.prev = newNode; + newNode.next = this.tail; +} +``` + +(3)删除节点 + +```java +public void remove(E value) { + DListNode prev = this.head; + while (prev.next != this.tail) { + DListNode curr = prev.next; + if (curr.value.equals(value)) { + prev.next = curr.next; + curr.next.prev = prev; + curr.next = null; + curr.prev = null; + break; + } + prev = prev.next; + } +} +``` + +(4)查找节点 + +```java +public DListNode find(E value) { + DListNode node = this.head.next; + while (node != this.tail) { + if (node.value.equals(value)) { + return node; + } + node = node.next; + } + return null; +} +``` + +## 练习 + +- 数组 + - [x] [724. 寻找数组的中心下标](https://leetcode-cn.com/problems/find-pivot-index/) + - [x] [35. 搜索插入位置](https://leetcode-cn.com/problems/search-insert-position/) + - [x] [56. 合并区间](https://leetcode-cn.com/problems/merge-intervals/) +- 链表 + - [ ] [设计链表](https://leetcode-cn.com/leetbook/read/linked-list/jy291/) + - [ ] [环形链表](https://leetcode-cn.com/leetbook/read/linked-list/jbex5/) + - [ ] [环形链表 II](https://leetcode-cn.com/leetbook/read/linked-list/jjhf6/) + - [ ] [相交链表](https://leetcode-cn.com/leetbook/read/linked-list/jjbj2/) + - [ ] [删除链表的倒数第 N 个节点](https://leetcode-cn.com/leetbook/read/linked-list/jf1cc/) + - [ ] [反转链表](https://leetcode-cn.com/leetbook/read/linked-list/f58sg/) + - [ ] [移除链表元素](https://leetcode-cn.com/leetbook/read/linked-list/f9izv/) + - [ ] [奇偶链表](https://leetcode-cn.com/leetbook/read/linked-list/fe0kj/) + - [ ] [回文链表](https://leetcode-cn.com/leetbook/read/linked-list/fov6t/) + - [ ] [合并两个有序链表](https://leetcode-cn.com/leetbook/read/linked-list/fnzd1/) + - [ ] [两数相加](https://leetcode-cn.com/leetbook/read/linked-list/fv6w7/) + - [ ] [扁平化多级双向链表](https://leetcode-cn.com/leetbook/read/linked-list/fw8v5/) + - [ ] [复制带随机指针的链表](https://leetcode-cn.com/leetbook/read/linked-list/fdi26/) + - [ ] [旋转链表](https://leetcode-cn.com/leetbook/read/linked-list/f00a2/) + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) +- [数据结构(C 语言版)](https://item.jd.com/12407475.html) +- [数据结构(C++语言版)](https://book.douban.com/subject/25859528/) +- [Leetcode:数组和字符串](https://leetcode-cn.com/leetbook/detail/array-and-string/) +- [Leetcode:链表](https://leetcode-cn.com/tag/linked-list/) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" new file mode 100644 index 0000000..2df6ac1 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" @@ -0,0 +1,109 @@ +--- +title: 栈和队列 +categories: + - 数据结构和算法 +tags: + - 数据结构 + - 线性表 + - 栈 + - 队列 +abbrlink: 8d66b5f2 +date: 2014-01-25 16:46:13 +permalink: /pages/1f15c3/ +--- + +# 栈和队列 + +> **队列**和**栈**都是**操作受限**的**线性表**:前者先进先出,后者先进后出。 + +## 栈 + +### 栈是什么 + +在 **LIFO(后进先出)** 数据结构中,将首先处理添加到队列中的最新元素。 + +**栈是一个 LIFO(后进先出) 数据结构**。**栈是一种“操作受限”的线性表**,只允许在一端插入和删除数据。通常,插入操作在栈中被称作入栈 push 。与队列类似,总是在堆栈的末尾添加一个新元素。但是,删除操作,退栈 pop ,将始终删除队列中相对于它的最后一个元素。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320200148.png) + +**当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构**。 + +从栈的定义可以看出,栈只支持两个基本操作:**入栈 `push()`** 和 **出栈 `pop()`** ,也就是在栈顶插入一个数据和从栈顶删除一个数据。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 `O(1)`。 + +栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作**顺序栈**,用链表实现的栈,我们叫作**链式栈**。 + +### 为什么需要栈 + +相比数组和链表,栈只是对操作进行了限制,似乎并没有任何优势。为什么不直接使用数组或者链表?为什么还要用这个“操作受限”的“栈”呢? + +特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。 + +### 栈的应用场景 + +(1)**函数调用栈** + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310091000.jpg) + +(2)**表达式求值** + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310091100.jpg) + +(3)**表达式匹配** + +可以借助栈来检查表达式中的括号是否匹配 + +## 队列 + +在 FIFO 数据结构中,将首先处理添加到队列中的第一个元素。 + +队列是典型的 FIFO 数据结构。插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾。 删除(delete)操作也被称为出队(dequeue)。 你只能移除第一个元素。 + +### 什么是队列 + +**队列:先进先出的线性表**。 + +**队列是一种“操作受限”的线性表**,只允许在一端插入数据,在另一端删除数据。 + +队列的最基本操作:**入队 `enqueue()`**,放一个数据到队列尾部;**出队 `dequeue()`**,从队列头部取一个元素。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320200213.png) + +队列可以用数组来实现,也可以用链表来实现。用数组实现的队列叫作**顺序队列**,用链表实现的队列叫作**链式队列**。 + +队满的判断条件是 `tail == n`,队空的判断条件是 `head == tail`。 + +### 循环队列 + +循环队列是一种较为特殊的队列。 + +循环队列的要点是确定好 **队空和队满的判定条件**。 + +在用数组实现的非循环队列中,队满的判断条件是 `(tail+1) % n == head`,队空的判断条件是 `head == tail`。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220322214822.png) + +### 为什么需要队列 + +为什么需要队列和为什么需要栈,是同样的道理,参考 为什么需要栈 + +### 队列的应用场景 + +(1)**阻塞队列** + +**阻塞队列**其实就是在队列基础上增加了阻塞操作。简单来说,就是: + +- 在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回; +- 如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310092908.jpg) + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310093026.jpg) + +(2)**并发队列** + +线程安全的队列我们叫作**并发队列**。最简单直接的实现方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。实际上,基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因。 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) +- [Leetcode:栈和队列](https://leetcode-cn.com/leetbook/detail/queue-stack/) diff --git a/docs/algorithm/search/linear-list-search.md "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" similarity index 81% rename from docs/algorithm/search/linear-list-search.md rename to "docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" index ee7882e..1cfc918 100644 --- a/docs/algorithm/search/linear-list-search.md +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" @@ -1,6 +1,20 @@ +--- +title: 线性表的查找 +categories: + - 数据结构和算法 + - 线性表 +tags: + - 数据结构 + - 线性表 + - 查找 +abbrlink: 443d1da2 +date: 2015-03-10 18:29:13 +permalink: /pages/4b1ed0/ +--- + # 线性表的查找 -## 概念 +## 查找简介 ### 什么是查找? @@ -18,11 +32,11 @@ ### 查找算法性能比较的标准 -**——平均查找长度ASL(Average Search Length)** +**——平均查找长度 ASL(Average Search Length)** 由于查找算法的主要运算是关键字的比较过程,所以通常把查找过程中对关键字需要执行的**平均比较长度**(也称为**平均比较次数**)作为衡量一个查找算法效率优劣的比较标准。 -
+![img](https://upload-images.jianshu.io/upload_images/3101171-a38f84148d091364.gif?imageMogr2/auto-orient/strip) **选取查找算法的因素** @@ -42,9 +56,9 @@ **基本思想** -从数据结构线形表的**一端**开始,**顺序扫描**,**依次**将扫描到的结点关键字与给定值k相**比较**,若相等则表示查找成功; +从数据结构线形表的**一端**开始,**顺序扫描**,**依次**将扫描到的结点关键字与给定值 k 相**比较**,若相等则表示查找成功; -若扫描结束仍没有找到关键字等于k的结点,表示查找失败。 +若扫描结束仍没有找到关键字等于 k 的结点,表示查找失败。 **核心代码** @@ -56,7 +70,7 @@ public int orderSearch(int[] list, int length, int key) { return i; } } - + // 如果扫描完,说明没有元素的值匹配key,返回-1,表示查找失败 return -1; } @@ -76,9 +90,7 @@ ASL = (N + N-1 + ... + 2 + 1) / N = (N+1) / 2 ## 二分查找 -**要点** - -二分查找又称**折半查找**,它是一种效率较高的查找方法。 +> **二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0**。 **存储结构** @@ -124,6 +136,13 @@ public int binarySearch(int[] list, int length, int key) { 由此可知,二分查找的**平均查找长度**实际上就是树的高度**O(log2N)**。 +**二分查找的局限性** + +- 二分查找依赖的是顺序表结构,简单点说就是数组 +- 二分查找针对的是有序数据 +- 数据量太小不适合二分查找 +- 数据量太大也不适合二分查找 + ## 分块查找 **要点** @@ -138,19 +157,19 @@ public int binarySearch(int[] list, int length, int key) { 所谓**“分块有序”的线性表**,是指: -假设要排序的表为R[0...N-1],**将表均匀分成b块**,前b-1块中记录个数为s=N/b,最后一块记录数小于等于s; +假设要排序的表为 R[0...N-1],**将表均匀分成 b 块**,前 b-1 块中记录个数为 s=N/b,最后一块记录数小于等于 s; 每一块中的关键字不一定有序,但**前一块中的最大关键字必须小于后一块中的最小关键字**。 -***注:这是使用分块查找的前提条件。*** +**_注:这是使用分块查找的前提条件。_** -如上将表均匀分成b块后,抽取各块中的**最大关键字**和**起始位置**构成一个索引表IDX[0...b-1]。 +如上将表均匀分成 b 块后,抽取各块中的**最大关键字**和**起始位置**构成一个索引表 IDX[0...b-1]。 -由于表R是分块有序的,所以**索引表是一个递增有序表**。 +由于表 R 是分块有序的,所以**索引表是一个递增有序表**。 下图就是一个分块查找表的存储结构示意图 -
+![img](https://upload-images.jianshu.io/upload_images/3101171-b7ad44c68d0c3c75.png) **基本思想** @@ -168,7 +187,7 @@ public int binarySearch(int[] list, int length, int key) { **代码范例** -
+![img](http://upload-images.jianshu.io/upload_images/3101171-2737612c781e66e8.gif?imageMogr2/auto-orient/strip) ```java class BlockSearch { @@ -250,16 +269,16 @@ class BlockSearch { public static void main(String[] args) { int key = 85; - int array[] = { 8, 14, 6, 9, 10, 22, 34, 18, 19, 31, 40, 38, 54, 66, 46, 71, 78, 68, 80, 85 }; + int array2[] = { 8, 14, 6, 9, 10, 22, 34, 18, 19, 31, 40, 38, 54, 66, 46, 71, 78, 68, 80, 85 }; BlockSearch search = new BlockSearch(); System.out.print("线性表: "); - search.printAll(array); + search.printAll(array2); - IndexType[] idxGroup = search.createIndex(array, array.length, 5); + IndexType[] idxGroup = search.createIndex(array2, array2.length, 5); search.printIDX(idxGroup); - int pos = search.blockSearch(idxGroup, idxGroup.length, array, - array.length, key); + int pos = search.blockSearch(idxGroup, idxGroup.length, array2, + array2.length, key); if (-1 == pos) { System.out.format("查找key = %d失败", key); } else { @@ -273,7 +292,7 @@ class BlockSearch { **运行结果** ``` -线性表: 8 14 6 9 10 22 34 18 19 31 40 38 54 66 46 71 78 68 80 85 +线性表: 8 14 6 9 10 22 34 18 19 31 40 38 54 66 46 71 78 68 80 85 构造索引表如下: key = 14, link = 0 key = 34, link = 5 @@ -287,7 +306,7 @@ key = 85, link = 15 因为分块查找实际上是两次查找过程之和。若以二分查找来确定块,显然它的查找效率介于顺序查找和二分查找之间。 -## 三种线性查找的PK +## 三种线性查找的 PK (1) 以平均查找长度而言,二分查找 > 分块查找 > 顺序查找。 @@ -297,6 +316,4 @@ key = 85, link = 15 (4) 分块查找综合了顺序查找和二分查找的优点,既可以较为快速,也能使用动态变化的要求。 -## 资源 - -《数据结构习题与解析》(B级第3版) +## 参考资料 diff --git a/docs/algorithm/sort.md "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" similarity index 90% rename from docs/algorithm/sort.md rename to "docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" index b3e9588..2bee638 100644 --- a/docs/algorithm/sort.md +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" @@ -1,52 +1,22 @@ -# 细说排序算法 - -> :notebook: 本文已归档到:「[blog](https://github.com/dunwu/blog/tree/master/source/_posts/algorithm)」 +--- +title: 线性表的排序 +categories: + - 数据结构和算法 + - 线性表 +tags: + - 数据结构 + - 线性表 + - 排序 +abbrlink: d3b2b8db +date: 2015-03-03 17:37:24 +permalink: /pages/21c5f2/ +--- + +# 线性表的排序 + +> 📦 本文已归档到:「[blog](https://github.com/dunwu/blog/tree/master/source/_posts/algorithm)」 > -> :keyboard: 本文中的示例代码已归档到:「[algorithm-tutorial](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java)」 - - - -- [冒泡排序](#冒泡排序) - - [要点](#要点) - - [算法思想](#算法思想) - - [算法分析](#算法分析) - - [示例代码](#示例代码) -- [快速排序](#快速排序) - - [要点](#要点-1) - - [算法思想](#算法思想-1) - - [算法分析](#算法分析-1) - - [示例代码](#示例代码-1) -- [插入排序](#插入排序) - - [要点](#要点-2) - - [算法思想](#算法思想-2) - - [算法分析](#算法分析-2) - - [示例代码](#示例代码-2) -- [希尔排序](#希尔排序) - - [要点](#要点-3) - - [算法思想](#算法思想-3) - - [算法分析](#算法分析-3) - - [示例代码](#示例代码-3) -- [简单选择排序](#简单选择排序) - - [要点](#要点-4) - - [算法思想](#算法思想-4) - - [算法分析](#算法分析-4) - - [示例代码](#示例代码-4) -- [堆排序](#堆排序) - - [要点](#要点-5) - - [算法思想](#算法思想-5) - - [算法分析](#算法分析-5) - - [示例代码](#示例代码-5) -- [归并排序](#归并排序) - - [要点](#要点-6) - - [算法思想](#算法思想-6) - - [算法分析](#算法分析-6) - - [示例代码](#示例代码-6) -- [基数排序](#基数排序) - - [要点](#要点-7) - - [算法分析](#算法分析-7) - - [示例代码](#示例代码-7) - - +> 🔁 本文中的示例代码已归档到:「[algorithm-tutorial](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java)」 ## 冒泡排序 @@ -66,7 +36,7 @@ 假设有一个大小为 N 的无序序列。冒泡排序就是要每趟排序过程中通过两两比较,找到第 i 个小(大)的元素,将其往上排。 -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/bubble-sort.png) 以上图为例,演示一下冒泡排序的实际流程: @@ -209,7 +179,7 @@ public void bubbleSort_2(int[] list) { 详细的图解往往比大堆的文字更有说明力,所以直接上图: -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/quick-sort.png) 上图中,演示了快速排序的处理过程: @@ -314,7 +284,7 @@ private void quickSort(int[] list, int left, int right) { 在讲解直接插入排序之前,先让我们脑补一下我们打牌的过程。 -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/insert-sort.png) - 先拿一张 5 在手里, - 再摸到一张 4,比 5 小,插到 5 前面, @@ -414,7 +384,7 @@ public void insertSort(int[] list) { 我们来通过演示图,更深入的理解一下这个过程。 -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/shell-sort.png) 在上面这幅图中: @@ -520,7 +490,7 @@ Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长 **核心代码** -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/selection-sort.png) ### 算法分析 @@ -574,7 +544,7 @@ Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长 其中 i=1,2,…,n/2 向下取整; -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/heap-sort.png) 如上图所示,序列 R{3, 8,15, 31, 25} 是一个典型的小根堆。 @@ -608,42 +578,42 @@ Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长 设有一个无序序列 { 1, 3,4, 5, 2, 6, 9, 7, 8, 0 }。 -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/heap-sort-02.png) 构造了初始堆后,我们来看一下完整的堆排序处理: 还是针对前面提到的无序序列 { 1,3, 4, 5, 2, 6, 9, 7, 8, 0 } 来加以说明。 -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/heap-sort-03.png) 相信,通过以上两幅图,应该能很直观的演示堆排序的操作处理。 **核心代码** ```java -public void HeapAdjust(int[] array, int parent, int length) { - int temp = array[parent]; // temp保存当前父节点 +public void HeapAdjust(int[] array2, int parent, int length) { + int temp = array2[parent]; // temp保存当前父节点 int child = 2 * parent + 1; // 先获得左孩子 while (child < length) { // 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点 - if (child + 1 < length && array[child] < array[child + 1]) { + if (child + 1 < length && array2[child] < array2[child + 1]) { child++; } // 如果父结点的值已经大于孩子结点的值,则直接结束 - if (temp >= array[child]) + if (temp >= array2[child]) break; // 把孩子结点的值赋给父结点 - array[parent] = array[child]; + array2[parent] = array2[child]; // 选取孩子结点的左孩子结点,继续向下筛选 parent = child; child = 2 * child + 1; } - array[parent] = temp; + array2[parent] = temp; } public void heapSort(int[] list) { @@ -740,7 +710,7 @@ public void heapSort(int[] list) { **核心代码** ```java -public void Merge(int[] array, int low, int mid, int high) { +public void Merge(int[] array2, int low, int mid, int high) { int i = low; // i是第一段序列的下标 int j = mid + 1; // j是第二段序列的下标 int k = 0; // k是临时存放合并序列的下标 @@ -749,12 +719,12 @@ public void Merge(int[] array, int low, int mid, int high) { // 扫描第一段和第二段序列,直到有一个扫描结束 while (i <= mid && j <= high) { // 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描 - if (array[i] <= array[j]) { - array2[k] = array[i]; + if (array2[i] <= array2[j]) { + array2[k] = array2[i]; i++; k++; } else { - array2[k] = array[j]; + array2[k] = array2[j]; j++; k++; } @@ -762,28 +732,28 @@ public void Merge(int[] array, int low, int mid, int high) { // 若第一段序列还没扫描完,将其全部复制到合并序列 while (i <= mid) { - array2[k] = array[i]; + array2[k] = array2[i]; i++; k++; } // 若第二段序列还没扫描完,将其全部复制到合并序列 while (j <= high) { - array2[k] = array[j]; + array2[k] = array2[j]; j++; k++; } // 将合并序列复制到原始序列中 for (k = 0, i = low; i <= high; i++, k++) { - array[i] = array2[k]; + array2[i] = array2[k]; } } ``` 掌握了合并的方法,接下来,让我们来了解**如何分解**。 -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/merge-sort.png) 在某趟归并中,设各子表的长度为 **gap**,则归并前 R[0...n-1] 中共有 **n/gap** 个有序的子表:`R[0...gap-1]`, `R[gap...2*gap-1]`, ... , `R[(n/gap)*gap ... n-1]`。 @@ -794,17 +764,17 @@ public void Merge(int[] array, int low, int mid, int high) { **核心代码** ```java -public void MergePass(int[] array, int gap, int length) { +public void MergePass(int[] array2, int gap, int length) { int i = 0; // 归并gap长度的两个相邻子表 for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) { - Merge(array, i, i + gap - 1, i + 2 * gap - 1); + Merge(array2, i, i + gap - 1, i + 2 * gap - 1); } // 余下两个子表,后者长度小于gap if (i + gap - 1 < length) { - Merge(array, i, i + gap - 1, length - 1); + Merge(array2, i, i + gap - 1, length - 1); } } @@ -877,7 +847,7 @@ public int[] sort(int[] list) { 我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 50,个位数上是 0,将这个数存入编号为 0 的桶中。 -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/radix-sort.png) 分类后,我们在从各个桶中,将这些数按照从编号 0 到编号 9 的顺序依次将所有数取出来。 diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" new file mode 100644 index 0000000..8660f49 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" @@ -0,0 +1,183 @@ +--- +title: 树和二叉树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 二叉树 + - 完全二叉树 +abbrlink: dd5c0739 +date: 2014-06-15 15:39:23 +permalink: /pages/92e4c1/ +--- + +# 树和二叉树 + +## 树 + +### 什么是树 + +在计算机科学中,**树**(英语:tree)是一种[抽象数据类型](https://zh.wikipedia.org/wiki/抽象資料型別)(ADT)或是实现这种抽象数据类型的[数据结构](https://zh.wikipedia.org/wiki/資料結構),用来模拟具[有树状结构](https://zh.wikipedia.org/wiki/樹狀結構)性质的数据集合。它是由 n(n>0)个有限节点组成一个具有层次关系的[集合](https://zh.wikipedia.org/wiki/集合)。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 + +它具有以下的特点: + +- 每个节点都只有有限个子节点或无子节点。 +- 树有且仅有一个根节点。 +- 根节点没有父节点;非根节点有且仅有一个父节点。 +- 每个非根节点可以分为多个不相交的子树。 +- 树里面没有环路。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403163620.png) + +### 树的术语 + +- **节点的度**:一个节点含有的子树的个数称为该节点的度; +- **树的度**:一棵树中,最大的节点度称为树的度; +- **叶子节点**或**终端节点**:度为零的节点; +- **非终端节点**或**分支节点**:度不为零的节点; +- **父节点**:若一个节点含有子节点,则这个节点称为其子节点的父节点; +- **子节点**:一个节点含有的子树的根节点称为该节点的子节点; +- **兄弟节点**:具有相同父节点的节点互称为兄弟节点; +- **堂兄弟节点**:父节点在同一层的节点互为堂兄弟; +- **节点的祖先**:从根到该节点所经分支上的所有节点; +- **子孙**:以某节点为根的子树中任一节点都称为该节点的子孙。 +- **森林**:由 m(m>=0)棵互不相交的树的集合称为森林; + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403164732.png) + +- **节点的高度**:节点到叶子节点的最长路径。高度是从下往上度量。 +- **节点的深度**:根节点到该节点的最长路径。深度是从上往下度量。 +- **节点的层次**:节点的深度 + 1。 +- **树的高度**:根节点的高度。 + +### 树的性质 + +- 树中的节点数等于所有节点的度数加 1。 +- 度为 m 的树中第 `i` 层上至多有 $$m^{i-1}$$ 个节点($$i ≥ 1$$)。 +- 高度为 h 的 m 次树至多有 $$(m^h-1)/(m-1)$$ 个节点。 +- 具有 n 个节点的 m 次树的最小高度为 $$\log_m{(n(m-1)+1)}$$ 。 + +### 树的种类 + +**无序树**:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为[自由树](https://zh.wikipedia.org/wiki/自由树); + +**有序树**:树中任意节点的子节点之间有顺序关系,这种树称为有序树; + +- 二叉树:每个节点最多含有两个子树的树称为二叉树; + - **完全二叉树**:对于一颗二叉树,假设其深度为 d(d>1)。除了第 d 层外,其它各层的节点数目均已达最大值,且第 d 层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树; +- [满二叉树](https://zh.wikipedia.org/wiki/满二叉树):所有叶节点都在最底层的完全二叉树; +- [平衡二叉树](https://zh.wikipedia.org/wiki/平衡二叉树)([AVL 树](https://zh.wikipedia.org/wiki/AVL树)):当且仅当任何节点的两棵子树的高度差不大于 1 的二叉树; +- [排序二叉树](https://zh.wikipedia.org/wiki/排序二元樹)([二叉查找树](https://zh.wikipedia.org/wiki/二叉查找树)(英语:Binary Search Tree)):也称二叉搜索树、有序二叉树; +- [霍夫曼树](https://zh.wikipedia.org/wiki/霍夫曼树):[带权路径](https://zh.wikipedia.org/w/index.php?title=带权路径&action=edit&redlink=1)最短的二叉树称为哈夫曼树或最优二叉树; +- [B 树](https://zh.wikipedia.org/wiki/B树):一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。 + +## 二叉树 + +二叉树中的每个节点最多有两个子节点,分别是**左子节点**和**右子节点**。 + +### 二叉树的性质 + +1. 二叉树第 i 层上的结点数目最多为 **2i-1** (i≥1)。 +2. 深度为 k 的二叉树至多有 **2k-1** 个结点(k≥1)。 +3. 包含 n 个结点的二叉树的高度至少为 **log2(n+1)**。 +4. 在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1。 + +### 满二叉树 + +除了叶子节点之外,每个节点都有左右两个子节点,这种二叉树就叫作**满二叉树**。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403183927.png) + +### 完全二叉树 + +叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大,这种二叉树叫作**完全二叉树**。 + +特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403183640.png) + +存储一棵二叉树,有两种方法,一种是基于指针或者引用的二叉链式存储法,一种是基于数组的顺序存储法。 + +**二叉链式存储法** + +每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403212249.png) + +**顺序存储法** + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403214627.png) + +如果节点 X 存储在数组中下标为 i 的位置,下标为 2 _ i 的位置存储的就是左子节点,下标为 2 _ i + 1 的位置存储的就是右子节点。反过来,下标为 i/2 的位置存储就是它的父节点。通过这种方式,我们只要知道根节点存储的位置(一般情况下,为了方便计算子节点,根节点会存储在下标为 1 的位置),这样就可以通过下标计算,把整棵树都串起来。 + +如果是非完全二叉树,其实会浪费比较多的数组存储空间。所以,如果某棵二叉树是一棵完全二叉树,那用数组存储无疑是最节省内存的一种方式。因为数组的存储方式并不需要像链式存储法那样,要存储额外的左右子节点的指针。这也是 为什么完全二叉树要求最后一层的子节点都靠左的原因。 + +### 二叉树的遍历 + +二叉树的遍历有三种方式: + +- **前序遍历**:对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。 +- **中序遍历**:对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。 +- **后序遍历**是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220404201713.png) + +## 二叉查找树 + +二叉查找树是二叉树中最常用的一种类型,也叫二叉搜索树。顾名思义,二叉查找树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。 + +**二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。** + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405172359.png) + +### 二叉查找树的查找 + +首先,我们看如何在二叉查找树中查找一个节点。我们先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405172537.png) + +### 二叉查找树的插入 + +如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405172549.png) + +### 二叉查找树的删除 + +第一种情况是,如果要删除的节点没有子节点,我们只需要直接将父节点中,指向要删除节点的指针置为 null。 + +第二种情况是,如果要删除的节点只有一个子节点(只有左子节点或者右子节点),我们只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405200219.png) + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405200234.png) + +第三种情况是,如果要删除的节点有两个子节点,这就比较复杂了。我们需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),所以,我们可以应用上面两条规则来删除这个最小节点。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405200456.png) + +### 二叉查找树的时间复杂度 + +不管操作是插入、删除还是查找,**时间复杂度其实都跟树的高度成正比,也就是 O(log n)**。 + +二叉查找树的形态各式各样。比如这个图中,对于同一组数据,我们构造了三种二叉查找树。它们的查找、插入、删除操作的执行效率都是不一样的。图中第一种二叉查找树,根节点的左右子树极度不平衡,已经退化成了链表,所以查找的时间复杂度就变成了 O(n)。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405234630.png) + +### 为什么需要二叉查找树 + +第一,哈希表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。 + +第二,哈希表扩容耗时很多,而且当遇到散列冲突时,性能不稳定,尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。 + +第三,笼统地来说,尽管哈希表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。 + +第四,哈希表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。 + +最后,为了避免过多的散列冲突,哈希表装载因子不能太大,特别是基于开放寻址法解决冲突的哈希表,不然会浪费一定的存储空间。 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" new file mode 100644 index 0000000..c66e6e1 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" @@ -0,0 +1,60 @@ +--- +title: 堆 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 二叉树 + - 堆 +abbrlink: fab451a5 +date: 2015-03-09 16:01:27 +permalink: /pages/ce297c/ +--- + +# 堆 + +## 什么是堆? + +堆(Heap)是一个可以被看成近似完全二叉树的数组。 + +- **堆是一个完全二叉树**。完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列。 +- **堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值**。 + +堆可以分为大顶堆和小顶堆。 + +- 对于每个节点的值都大于等于子树中每个节点值的堆,叫作“**大顶堆**”。 + +- 对于每个节点的值都小于等于子树中每个节点值的堆,叫作“**小顶堆**”。 + +## 如何实现堆 + +完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为我们不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220311112542.jpg) + +堆常见的操作: + +- HEAPIFY 建堆:把一个乱序的数组变成堆结构的数组,时间复杂度为 $$O(n)$$。 +- HEAPPUSH:把一个数值放进已经是堆结构的数组中,并保持堆结构,时间复杂度为 $$O(log N)$$ +- HEAPPOP:从最大堆中取出最大值或从最小堆中取出最小值,并将剩余的数组保持堆结构,时间复杂度为 $$O(log N)$$。 +- HEAPSORT:借由 HEAPFY 建堆和 HEAPPOP 堆数组进行排序,时间复杂度为$$ O(N log N)$$,空间复杂度为 $$O(1)$$。 + +## 堆的应用场景 + +### 求 TOP N + +堆结构的一个常见应用是建立优先队列(Priority Queue)。 + +求 Top K 的问题抽象成两类。一类是针对静态数据集合;另一类是针对动态数据集合 + +### 优先级队列 + +在优先级队列中,数据的出队顺序不是先进先出,而是按照优先级来,优先级最高的,最先出队。 + +堆和优先级队列非常相似:往优先级队列中插入一个元素,就相当于往堆中插入一个元素;从优先级队列中取出优先级最高的元素,就相当于取出堆顶元素。 + +> 参考:Java 的 `PriorityQueue` 类 + +### 求中位数 diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" new file mode 100644 index 0000000..908780d --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" @@ -0,0 +1,63 @@ +--- +title: B+树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 二叉树 + - B+ 树 +abbrlink: 17426722 +date: 2022-03-13 22:37:27 +permalink: /pages/3fd76e/ +--- + +# B+树 + +## 什么是 B+树 + +B+树是在二叉查找树的基础上进行了改造:树中的节点并不存储数据本身,而是只是作为索引。每个叶子节点串在一条链表上,链表中的数据是从小到大有序的。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220311092926.jpg) + +改造之后,如果我们要求某个区间的数据。我们只需要拿区间的起始值,在树中进行查找,当查找到某个叶子节点之后,我们再顺着链表往后遍历,直到链表中的结点数据值大于区间的终止值为止。所有遍历到的数据,就是符合区间值的所有数据。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220311092929.jpg) + +但是,我们要为几千万、上亿的数据构建索引,如果将索引存储在内存中,尽管内存访问的速度非常快,查询的效率非常高,但是,占用的内存会非常多。 + +比如,我们给一亿个数据构建二叉查找树索引,那索引中会包含大约 1 亿个节点,每个节点假设占用 16 个字节,那就需要大约 1GB 的内存空间。给一张表建立索引,我们需要 1GB 的内存空间。如果我们要给 10 张表建立索引,那对内存的需求是无法满足的。如何解决这个索引占用太多内存的问题呢? + +我们可以借助时间换空间的思路,把索引存储在硬盘中,而非内存中。我们都知道,硬盘是一个非常慢速的存储设备。通常内存的访问速度是纳秒级别的,而磁盘访问的速度是毫秒级别的。读取同样大小的数据,从磁盘中读取花费的时间,是从内存中读取所花费时间的上万倍,甚至几十万倍。 + +这种将索引存储在硬盘中的方案,尽管减少了内存消耗,但是在数据查找的过程中,需要读取磁盘中的索引,因此数据查询效率就相应降低很多。 + +二叉查找树,经过改造之后,支持区间查找的功能就实现了。不过,为了节省内存,如果把树存储在硬盘中,那么每个节点的读取(或者访问),都对应一次磁盘 IO 操作。树的高度就等于每次查询数据时磁盘 IO 操作的次数。 + +我们前面讲到,比起内存读写操作,磁盘 IO 操作非常耗时,所以我们优化的重点就是尽量减少磁盘 IO 操作,也就是,尽量降低树的高度。那如何降低树的高度呢? + +我们来看下,如果我们把索引构建成 m 叉树,高度是不是比二叉树要小呢?如图所示,给 16 个数据构建二叉树索引,树的高度是 4,查找一个数据,就需要 4 个磁盘 IO 操作(如果根节点存储在内存中,其他结点存储在磁盘中),如果对 16 个数据构建五叉树索引,那高度只有 2,查找一个数据,对应只需要 2 次磁盘操作。如果 m 叉树中的 m 是 100,那对一亿个数据构建索引,树的高度也只是 3,最多只要 3 次磁盘 IO 就能获取到数据。磁盘 IO 变少了,查找数据的效率也就提高了。 + +## 为什么需要 B+树 + +关系型数据库中常用 B+ 树作为索引,这是为什么呢? + +思考以下经典应用场景 + +- 根据某个值查找数据,比如 `select * from user where id=1234`。 +- 根据区间值来查找某些数据,比如 `select * from user where id > 1234 and id < 2345`。 + +为了提高查询效率,需要使用索引。而对于索引的性能要求,主要考察**执行效率和存储空间**。如果让你选择一种数据结构去存储索引,你会如何考虑? + +以一些常见数据结构为例: + +- **哈希表**:哈希表的查询性能很好,时间复杂度是 `O(1)`。但是,哈希表不能支持按照区间快速查找数据。所以,哈希表不能满足我们的需求。 +- **平衡二叉查找树**:尽管平衡二叉查找树查询的性能也很高,时间复杂度是 `O(logn)`。而且,对树进行中序遍历,我们还可以得到一个从小到大有序的数据序列,但这仍然不足以支持按照区间快速查找数据。 +- **跳表**:跳表是在链表之上加上多层索引构成的。它支持快速地插入、查找、删除数据,对应的时间复杂度是 `O(logn)`。并且,跳表也支持按照区间快速地查找数据。我们只需要定位到区间起点值对应在链表中的结点,然后从这个结点开始,顺序遍历链表,直到区间终点对应的结点为止,这期间遍历得到的数据就是满足区间值的数据。 + +实际上,数据库索引所用到的数据结构跟跳表非常相似,叫作 B+ 树。不过,它是通过二叉查找树演化过来的,而非跳表。B+树的应用场景 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" new file mode 100644 index 0000000..c5d67cd --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" @@ -0,0 +1,79 @@ +--- +title: LSM树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - LSM 树 +abbrlink: 5bf5ed66 +date: 2022-03-16 09:27:21 +permalink: /pages/4a217d/ +--- + +# LSM 树 + +## 什么是 LSM 树 + +LSM 树具有以下 3 个特点: + +1. 将索引分为内存和磁盘两部分,并在内存达到阈值时启动树合并(Merge Trees); +2. 用批量写入代替随机写入,并且用预写日志 WAL 技术(Write AheadLog,预写日志技术)保证内存数据,在系统崩溃后可以被恢复; +3. 数据采取类似日志追加写的方式写入(Log Structured)磁盘,以顺序写的方式提高写 + 入效率。 + +LSM 树的这些特点,使得它相对于 B+ 树,在写入性能上有大幅提升。所以,许多 NoSQL 系统都使用 LSM 树作为检索引擎,而且还对 LSM 树进行了优化以提升检索性能。 + +LSM 树就是根据这个思路设计了这样一个机制:当数据写入时,延迟写磁盘,将数据先存放在内存中的树里,进行常规的存储和查询。当内存中的树持续变大达到阈值时,再批量地以块为单位写入磁盘的树中。因此,LSM 树至少需要由两棵树组成,一棵是存储在内存中较小的 C0 树,另一棵是存储在磁盘中较大的 C1 树。 + +### 如何将内存数据与磁盘数据合并 + +可以参考两个有序链表归并排序的过程,将 C0 树和 C1 树的所有叶子节点中存储的数据,看作是两个有序链表,那滚动合并问题就变成了我们熟悉的两个有序链表的归并问题。不过由于涉及磁盘操作,那为了提高写入效率和检索效率,我们还需要针对磁盘的特性,在一些归并细节上进行优化。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316105440.png) + +由于磁盘具有顺序读写效率高的特性,因此,为了提高 C1 树中节点的读写性能,除了根节点以外的节点都要尽可能地存放到连续的块中,让它们能作为一个整体单位来读写。这种包含多个节点的块就叫作多页块(Multi-Pages Block)。 + +第一步,以多页块为单位,将 C1 树的当前叶子节点从前往后读入内存。读入内存的多页块,叫作清空块(Emptying Block),意思是处理完以后会被清空。 + +第二步,将 C0 树的叶子节点和清空块中的数据进行归并排序,把归并的结果写入内存的一个新块中,叫作填充块(Filling Block)。 + +第三步,如果填充块写满了,我们就要将填充块作为新的叶节点集合顺序写入磁盘。这个时候,如果 C0 树的叶子节点和清空块都没有遍历完,我们就继续遍历归并,将数据写入新的填充块。如果清空块遍历完了,我们就去 C1 树中顺序读取新的多页块,加载到清空块中。 + +第四步,重复第三步,直到遍历完 C0 树和 C1 树的所有叶子节点,并将所有的归并结果写入到磁盘。这个时候,我们就可以同时删除 C0 树和 C1 树中被处理过的叶子节点。这样就完成了滚动归并的过程。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316110736.png) + +### LSM 树是如何检索 + +因为同时存在 C0 和 C1 树,所以要查询一个 key 时,我们会先到 C0 树中查询。如果查询到了则直接返回;如过没有查询到,则查询 C1 树。 + +需要注意一种特殊情况:删除操作。假设某数据在 C0 树中被删除了,但是在 C1 树中仍存在。这此时查询时,可以在 C1 树中查到这个 key,这其实是过期数据了,如何应对这种情况呢?对于被删除的数据,可以将这些数据的 key 插入到 C0 树中,并标记一个删除标志。如果查到了一个带着删除标志的 key,就直接返回查询失败。 + +## 为什么需要 LSM 树 + +在关系型数据库中,通常使用 B+ 树作为索引。B+ 树的数据都存储在叶子节点中,而叶子节点一般都存储在磁盘中。因此,每次插入的新数据都需要随机写入磁盘,而随机写入的性能非常慢。如果是一个日志系统,每秒钟要写入上千条甚至上万条数据,这样的磁盘操作代价会使得系统性能急剧下降,甚至无法使用。 + +操作系统对磁盘的读写是以块为单位的,我们能否以块为单位写入,而不是每次插入一个数据都要随机写入磁盘呢?这样是不是就可以大幅度减少写入操作了呢?解决方案就是:**LSM 树**(Log Structured Merge Trees)。 + +## WAL 技术 + +LSM 树至少需要由两棵树组成,一棵是存储在内存中较小的 C0 树,另一棵是存储在磁盘中较大的 C1 树。 + +如果机器断电或系统崩溃了,那内存中还未写入磁盘的数据岂不就永远丢失了?这种情况我们该如何解决呢? + +为了保证内存中的数据在系统崩溃后能恢复,可以使用 WAL 技术(Write Ahead Log,预写日志技术)将数据第一时间高效写入磁盘进行备份。 + +WAL 技术保存和恢复数据的具体步骤如下: + +1. 内存中的程序在处理数据时,会先将对数据的修改作为一条记录,顺序写入磁盘的 log 文件作为备份。由于磁盘文件的顺序追加写入效率很高,因此许多应用场景都可以接受这种备份处理。 +2. 在数据写入 log 文件后,备份就成功了。接下来,该数据就可以长期驻留在内存中了。 +3. 系统会周期性地检查内存中的数据是否都被处理完了(比如,被删除或者写入磁盘),并且生成对应的检查点(Check Point)记录在磁盘中。然后,我们就可以随时删除被处理完的数据了。这样一来,log 文件就不会无限增长了。 +4. 系统崩溃重启,我们只需要从磁盘中读取检查点,就能知道最后一次成功处理的数据在 log 文件中的位置。接下来,我们就可以把这个位置之后未被处理的数据,从 log 文件中读出,然后重新加载到内存中。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316104837.png) + +## 参考资料 + +- [检索技术核心 20 讲](https://time.geekbang.org/column/intro/100048401) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" new file mode 100644 index 0000000..d5916a6 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" @@ -0,0 +1,98 @@ +--- +title: 字典树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 字典树 +abbrlink: eea60a6a +date: 2022-03-13 22:37:27 +permalink: /pages/0a4984/ +--- + +# 字典树 + +## 什么是字典树 + +Trie 树(又叫「前缀树」或「字典树」)是一种用于快速查询「某个字符串/字符前缀」是否存在的数据结构。 + +- 根节点(Root)不包含字符,除根节点外的每一个节点都仅包含一个字符; +- 从根节点到某一节点路径上所经过的字符连接起来,即为该节点对应的字符串; +- 任意节点的所有子节点所包含的字符都不相同; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181057.jpg) + +### 字典树的构造 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181243.jpg) + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181425.jpg) + +构建 Trie 树的过程,需要扫描所有的字符串,时间复杂度是 O(n)(n 表示所有字符串的长度和)。 + +**字典树非常耗费内存**。 + +用数组来存储一个节点的子节点的指针。如果字符串中包含从 a 到 z 这 26 个字符,那每个节点都要存储一个长度为 26 的数组,并且每个数组存储一个 8 字节指针(或者是 4 字节,这个大小跟 CPU、操作系统、编译器等有关)。而且,即便一个节点只有很少的子节点,远小于 26 个,比如 3、4 个,我们也要维护一个长度为 26 的数组。 + +用数组来存储一个节点的子节点的指针。如果字符串中包含从 a 到 z 这 26 个字符,那每个节点都要存储一个长度为 26 的数组,并且每个数组存储一个 8 字节指针(或者是 4 字节,这个大小跟 CPU、操作系统、编译器等有关)。而且,即便一个节点只有很少的子节点,远小于 26 个,比如 3、4 个,我们也要维护一个长度为 26 的数组。 + +用数组来存储一个节点的子节点的指针。如果字符串中包含从 a 到 z 这 26 个字符,那每个节点都要存储一个长度为 26 的数组,并且每个数组存储一个 8 字节指针(或者是 4 字节,这个大小跟 CPU、操作系统、编译器等有关)。而且,即便一个节点只有很少的子节点,远小于 26 个,比如 3、4 个,我们也要维护一个长度为 26 的数组。 + +### 字典树的查找 + +1. 每次从根结点开始搜索; +2. 获取关键词的第一个字符,根据该字符选择对应的子节点,转到该子节点继续检索; +3. 在相应的子节点上,获取关键词的第二个字符,进一步选择对应的子节点进行检索; +4. 以此类推,进行迭代过程; +5. 在某个节点处,关键词的所有字母已被取出,则读取附在该节点上的信息,查找完成。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181305.jpg) + +每次查询时,如果要查询的字符串长度是 k,那我们只需要比对大约 k 个节点,就能完成查询操作。跟原本那组字符串的长度和个数没有任何关系。所以说,构建好 Trie 树后,在其中查找字符串的时间复杂度是 O(k),k 表示要查找的字符串的长度。 + +## 字典树的应用场景 + +在一组字符串中查找字符串,Trie 树实际上表现得并不好。它对要处理的字符串有及其严苛的要求。 + +第一,字符串中包含的字符集不能太大。我们前面讲到,如果字符集太大,那存储空间可能就会浪费很多。即便可以优化,但也要付出牺牲查询、插入效率的代价。 + +第二,要求字符串的前缀重合比较多,不然空间消耗会变大很多。 + +第三,如果要用 Trie 树解决问题,那我们就要自己从零开始实现一个 Trie 树,还要保证没有 bug,这个在工程上是将简单问题复杂化,除非必须,一般不建议这样做。 + +第四,我们知道,通过指针串起来的数据块是不连续的,而 Trie 树中用到了指针,所以,对缓存并不友好,性能上会打个折扣。 + +在一组字符串中查找字符串,Trie 树实际上表现得并不好。它对要处理的字符串有及其严苛的要求。 + +在一组字符串中查找字符串,Trie 树实际上表现得并不好。它对要处理的字符串有及其严苛的要求。 + +(1)自动补全 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305095300.png) + +(2)拼写检查 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305101637.png) + +(3)IP 路由 (最长前缀匹配) + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305102959.gif) + +图 3. 使用 Trie 树的最长前缀匹配算法,Internet 协议(IP)路由中利用转发表选择路径。 + +(4)T9 (九宫格) 打字预测 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305103047.jpg) + +(5)单词游戏 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305103052.png) + +Trie 树可通过剪枝搜索空间来高效解决 Boggle 单词游戏 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) +- https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode/ diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" new file mode 100644 index 0000000..dde06c0 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" @@ -0,0 +1,171 @@ +--- +title: 红黑树 +categories: + - 数据结构和算法 + - 树 +tags: + - 数据结构 + - 树 + - 二叉树 + - 红黑树 +abbrlink: f89cb603 +date: 2018-06-01 21:10:23 +permalink: /pages/0a4414/ +--- + +# 红黑树 + +## 平衡二叉树 + +平衡二叉树的严格定义是这样的:二叉树中任意一个节点的左右子树的高度相差不能大于 1。 + +完全二叉树、满二叉树其实都是平衡二叉树,但是非完全二叉树也有可能是平衡二叉树。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310202113.jpg) + +**平衡二叉查找树中“平衡”的意思,其实就是让整棵树左右看起来比较“对称”、比较“平衡”,不要出现左子树很高、右子树很矮的情况。这样就能让整棵树的高度相对来说低一些,相应的插入、删除、查找等操作的效率高一些**。 + +## 什么是红黑树 + +红黑树的英文是“Red-Black Tree”,简称 R-B Tree。它是一种不严格的平衡二叉查找树。 + +红黑树中的节点,一类被标记为黑色,一类被标记为红色。除此之外,一棵红黑树还需要满足这样几个要求: + +- 根节点是黑色的; +- 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据; +- 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的; +- 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点; + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310202612.jpg) + +### 为什么说红黑树是“近似平衡”的? + +平衡二叉查找树的初衷,是为了解决二叉查找树因为动态更新导致的性能退化问题。 + +所以,**“平衡”的意思可以等价为性能不退化。“近似平衡”就等价为性能不会退化的太严重**。 + +**如果我们将红色节点从红黑树中去掉,那单纯包含黑色节点的红黑树的高度是多少呢**? + +红色节点删除之后,有些节点就没有父节点了,它们会直接拿这些节点的祖父节点(父节点的父节点)作为父节点。所以,之前的二叉树就变成了四叉树。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310202902.jpg) + +前面红黑树的定义里有这么一条:从任意节点到可达的叶子节点的每个路径包含相同数目的黑色节点。我们从四叉树中取出某些节点,放到叶节点位置,四叉树就变成了完全二叉树。所以,仅包含黑色节点的四叉树的高度,比包含相同节点个数的完全二叉树的高度还要小。 + +**现在把红色节点加回去,高度会变成多少呢**? + +在红黑树中,红色节点不能相邻,也就是说,有一个红色节点就要至少有一个黑色节点,将它跟其他红色节点隔开。红黑树中包含最多黑色节点的路径不会超过 log2n,所以加入红色节点之后,最长路径不会超过 2log2n,也就是说,红黑树的高度近似 2log2n。 + +所以,红黑树的高度只比高度平衡的 AVL 树的高度(log2n)仅仅大了一倍,在性能上,下降得并不多。这样推导出来的结果不够精确,实际上红黑树的性能更好。 + +## 为什么需要红黑树 + +AVL 树是一种高度平衡的二叉树,所以查找的效率非常高,但是,有利就有弊,AVL 树为了维持这种高度的平衡,就要付出更多的代价。每次插入、删除都要做调整,就比较复杂、耗时。所以,对于有频繁的插入、删除操作的数据集合,使用 AVL 树的代价就有点高了。 + +红黑树只是做到了近似平衡,并不是严格的平衡,所以在维护平衡的成本上,要比 AVL 树要低。 + +所以,红黑树的插入、删除、查找各种操作性能都比较稳定。对于工程应用来说,要面对各种异常情况,为了支撑这种工业级的应用,我们更倾向于这种性能稳定的平衡二叉查找树。 + +## 红黑树平衡调整 + +### 插入操作的平衡调整 + +**红黑树规定,插入的节点必须是红色的。而且,二叉查找树中新插入的节点都是放在叶子节点上**。 + +- 如果插入节点的父节点是黑色的,那我们什么都不用做,它仍然满足红黑树的定义。 +- 如果插入的节点是根节点,那我们直接改变它的颜色,把它变成黑色就可以了。 + +除此之外,其他情况都会违背红黑树的定义,于是我们就需要进行调整,调整的过程包含两种基础的操作:**左右旋转**和**改变颜色**。 + +红黑树的平衡调整过程是一个迭代的过程。我们把正在处理的节点叫作**关注节点**。关注节点会随着不停地迭代处理,而不断发生变化。最开始的关注节点就是新插入的节点。 + +新节点插入之后,如果红黑树的平衡被打破,那一般会有下面三种情况。我们只需要根据每种情况的特点,不停地调整,就可以让红黑树继续符合定义,也就是继续保持平衡。 + +**CASE 1:如果关注节点是 a,它的叔叔节点 d 是红色**,我们就依次执行下面的操作: + +- 将关注节点 a 的父节点 b、叔叔节点 d 的颜色都设置成黑色; +- 将关注节点 a 的祖父节点 c 的颜色设置成红色; +- 关注节点变成 a 的祖父节点 c; +- 跳到 CASE 2 或者 CASE 3。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310203600.jpg) + +**CASE 2:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的右子节点**,我们就依次执行下面的操作: + +- 关注节点变成节点 a 的父节点 b; +- 围绕新的关注节点 b 左旋; +- 跳到 CASE 3。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310203623.jpg) + +**CASE 3:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的左子节点**,我们就依次执行下面的操作: + +- 围绕关注节点 a 的祖父节点 c 右旋; +- 将关注节点 a 的父节点 b、兄弟节点 c 的颜色互换。 +- 调整结束。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310203645.jpg) + +### 删除操作的平衡调整 + +#### 针对删除节点初步调整 + +**CASE 1:如果要删除的节点是 a,它只有一个子节点 b**,那我们就依次进行下面的操作: + +- 删除节点 a,并且把节点 b 替换到节点 a 的位置,这一部分操作跟普通的二叉查找树的删除操作一样; +- 节点 a 只能是黑色,节点 b 也只能是红色,其他情况均不符合红黑树的定义。这种情况下,我们把节点 b 改为黑色; +- 调整结束,不需要进行二次调整。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310204215.jpg) + +**CASE 2:如果要删除的节点 a 有两个非空子节点,并且它的后继节点就是节点 a 的右子节点 c**。我们就依次进行下面的操作: + +- 如果节点 a 的后继节点就是右子节点 c,那右子节点 c 肯定没有左子树。我们把节点 a 删除,并且将节点 c 替换到节点 a 的位置。这一部分操作跟普通的二叉查找树的删除操作无异; +- 然后把节点 c 的颜色设置为跟节点 a 相同的颜色; +- 如果节点 c 是黑色,为了不违反红黑树的最后一条定义,我们给节点 c 的右子节点 d 多加一个黑色,这个时候节点 d 就成了“红 - 黑”或者“黑 - 黑”; +- 这个时候,关注节点变成了节点 d,第二步的调整操作就会针对关注节点来做。 + +**CASE 3:如果要删除的是节点 a,它有两个非空子节点,并且节点 a 的后继节点不是右子节点**,我们就依次进行下面的操作: + +- 找到后继节点 d,并将它删除,删除后继节点 d 的过程参照 CASE 1; +- 将节点 a 替换成后继节点 d; +- 把节点 d 的颜色设置为跟节点 a 相同的颜色; +- 如果节点 d 是黑色,为了不违反红黑树的最后一条定义,我们给节点 d 的右子节点 c 多加一个黑色,这个时候节点 c 就成了“红 - 黑”或者“黑 - 黑”; +- 这个时候,关注节点变成了节点 c,第二步的调整操作就会针对关注节点来做。 + +#### 针对关注节点进行二次调整 + +**CASE 1:如果关注节点是 a,它的兄弟节点 c 是红色的**,我们就依次进行下面的操作: + +- 围绕关注节点 a 的父节点 b 左旋; +- 关注节点 a 的父节点 b 和祖父节点 c 交换颜色; +- 关注节点不变; +- 继续从四种情况中选择适合的规则来调整。 + +**CASE 2:如果关注节点是 a,它的兄弟节点 c 是黑色的,并且节点 c 的左右子节点 d、e 都是黑色的**,我们就依次进行下面的操作: + +- 将关注节点 a 的兄弟节点 c 的颜色变成红色; +- 从关注节点 a 中去掉一个黑色,这个时候节点 a 就是单纯的红色或者黑色; +- 给关注节点 a 的父节点 b 添加一个黑色,这个时候节点 b 就变成了“红 - 黑”或者“黑 - 黑”; +- 关注节点从 a 变成其父节点 b; +- 继续从四种情况中选择符合的规则来调整。 + +**CASE 3:如果关注节点是 a,它的兄弟节点 c 是黑色,c 的左子节点 d 是红色,c 的右子节点 e 是黑色**,我们就依次进行下面的操作: + +- 围绕关注节点 a 的兄弟节点 c 右旋; +- 节点 c 和节点 d 交换颜色; +- 关注节点不变; +- 跳转到 CASE 4,继续调整。 + +**CASE 4:如果关注节点 a 的兄弟节点 c 是黑色的,并且 c 的右子节点是红色的**,我们就依次进行下面的操作: + +- 围绕关注节点 a 的父节点 b 左旋; +- 将关注节点 a 的兄弟节点 c 的颜色,跟关注节点 a 的父节点 b 设置成相同的颜色; +- 将关注节点 a 的父节点 b 的颜色设置为黑色; +- 从关注节点 a 中去掉一个黑色,节点 a 就变成了单纯的红色或者黑色; +- 将关注节点 a 的叔叔节点 e 设置为黑色; +- 调整结束。 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" new file mode 100644 index 0000000..a5b9ff1 --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" @@ -0,0 +1,157 @@ +--- +title: 哈希表 +categories: + - 数据结构和算法 +tags: + - 数据结构和算法 + - 哈希表 +abbrlink: 850f2080 +date: 2015-03-16 14:19:59 +permalink: /pages/b501c7/ +--- + +# 哈希表 + +> **哈希表** 是一种使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。 +> +> 有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 +> +> - **哈希集合** 是集合数据结构的实现之一,用于存储非重复值。 +> - **哈希映射** 是映射 数据结构的实现之一,用于存储(key, value)键值对。 + +## 什么是哈希表 + +哈希表的英文叫“Hash Table”,我们平时也叫它“散列表”或者“Hash 表”。 + +**哈希表** 是一种使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。 + +有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 + +- **哈希集合** 是集合数据结构的实现之一,用于存储非重复值。 +- **哈希映射** 是映射 数据结构的实现之一,用于存储(key, value)键值对。 + +**哈希表用的是数组支持按照下标随机访问数据的特性,所以哈希表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有哈希表**。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320201844.png) + +哈希表通过散列函数把元素的键值映射为下标,然后将数据存储在数组中对应下标的位置。按照键值查询元素时,用同样的散列函数,将键值转化数组下标,从对应的数组下标的位置取数据。 + +有两种不同类型的哈希表:哈希集合和哈希映射。 + +- `哈希集合`是`集合`数据结构的实现之一,用于存储`非重复值`。 +- `哈希映射`是`映射` 数据结构的实现之一,用于存储`(key, value)`键值对。 + +在`标准模板库`的帮助下,哈希表是`易于使用的`。大多数常见语言(如 Java,C ++ 和 Python)都支持哈希集合和哈希映射。 + +## 散列函数 + +散列函数,顾名思义,它是一个函数。我们可以把它定义成 **hash(key)**,其中 key 表示元素的键值,hash(key) 的值表示经过散列函数计算得到的散列值。 + +哈希表的关键思想是使用哈希函数将键映射到存储桶。更确切地说, + +1. 当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中; +2. 当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。 + +散列函数将取决于 `键值的范围` 和 `桶的数量` 。 + +**散列函数设计的基本要求**: + +1. 散列函数计算得到的散列值是一个非负整数; +2. 如果 key1 = key2,那 hash(key1) == hash(key2); +3. 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)。 + +### 散列冲突 + +即便像业界著名的[MD5](https://zh.wikipedia.org/wiki/MD5)、[SHA](https://zh.wikipedia.org/wiki/SHA家族)、[CRC](https://zh.wikipedia.org/wiki/循環冗餘校驗)等哈希算法,也无法完全避免这种**散列冲突**。 + +该如何解决散列冲突问题呢?我们常用的散列冲突解决方法有两类,开放寻址法(open addressing)和链表法(chaining)。 + +### 装载因子 + +当哈希表中空闲位置不多的时候,散列冲突的概率就会大大提高。为了尽可能保证哈希表的操作效率,一般情况下,我们会尽可能保证哈希表中有一定比例的空闲槽位。我们用**装载因子**(load factor)来表示空位的多少。 + +装载因子的计算公式是: + +``` +哈希表的装载因子 = 填入表中的元素个数 / 哈希表的长度 +``` + +**装载因子越大,说明空闲位置越少,冲突越多**,哈希表的性能会下降。不仅插入数据的过程要多次寻址或者拉很长的链,查找的过程也会因此变得很慢。 + +当装载因子过大时,就需要对哈希表扩容。新申请一个更大的哈希表,将数据搬移到这个新哈希表中。针对数组的扩容,数据搬移操作比较简单。但是,针对哈希表的扩容,数据搬移操作要复杂很多。因为哈希表的大小变了,数据的存储位置也变了,所以我们需要通过散列函数重新计算每个数据的存储位置。 + +插入一个数据,最好情况下,不需要扩容,最好时间复杂度是 O(1)。最坏情况下,哈希表装载因子过高,启动扩容,我们需要重新申请内存空间,重新计算哈希位置,并且搬移数据,所以时间复杂度是 O(n)。用摊还分析法,均摊情况下,时间复杂度接近最好情况,就是 O(1)。 + +装载因子阈值需要选择得当。如果太大,会导致冲突过多;如果太小,会导致内存浪费严重。 + +### 开放寻址法 + +开放寻址法的核心思想是,如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。 + +**当数据量比较小、装载因子小的时候,适合采用开放寻址法。这也是 Java 中的 `ThreadLocalMap` 使用开放寻址法解决散列冲突的原因**。 + +**线性探测**(Linear Probing):当我们往哈希表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323200359.png) + +对于使用线性探测法解决冲突的哈希表,删除操作稍微有些特别。我们不能单纯地把要删除的元素设置为空。这是为什么呢?在查找的时候,一旦我们通过线性探测方法,找到一个空闲位置,我们就可以认定哈希表中不存在这个数据。但是,如果这个空闲位置是我们后来删除的,就会导致原来的查找算法失效。本来存在的数据,会被认定为不存在。这个问题如何解决呢? + +我们可以将删除的元素,特殊标记为 deleted。当线性探测查找的时候,遇到标记为 deleted 的空间,并不是停下来,而是继续往下探测。 + +线性探测法其实存在很大问题。当哈希表中插入的数据越来越多时,散列冲突发生的可能性就会越来越大,空闲位置会越来越少,线性探测的时间就会越来越久。极端情况下,我们可能需要探测整个哈希表,所以最坏情况下的时间复杂度为 O(n)。同理,在删除和查找时,也有可能会线性探测整张哈希表,才能找到要查找或者删除的数据。 + +### 链表法 + +在哈希表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。 + +链表法比起开放寻址法,对大装载因子的容忍度更高。开放寻址法只能适用装载因子小于 1 的情况。接近 1 时,就可能会有大量的散列冲突,导致大量的探测、再散列等,性能会下降很多。但是对于链表法来说,只要散列函数的值随机均匀,即便装载因子变成 10,也就是链表的长度变长了而已,虽然查找效率有所下降,但是比起顺序查找还是快很多。 + +**基于链表的散列冲突处理方法比较适合存储大对象、大数据量的哈希表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表**。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323200419.png) + +当插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度是 O(1)。当查找、删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。那查找或删除操作的时间复杂度是多少呢? + +实际上,这两个操作的时间复杂度跟链表的长度 k 成正比,也就是 O(k)。对于散列比较均匀的散列函数来说,理论上讲,k=n/m,其中 n 表示散列中数据的个数,m 表示哈希表中“槽”的个数。 + +### 开放寻址法 vs. 链表法 + +**开放寻址法适用于数据量比较小、装载因子小的场景**。 + +**链表法适用于存储大对象、大数据量的哈希表**。**比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表**。 + +## 哈希表的应用场景 + +哈希算法的应用非常非常多,最常见的七个,分别是: + +- **安全加密**:如:MD5、SHA +- **唯一标识**:UUID +- 数据校验:数字签名 +- **散列函数**: +- **负载均衡**:会话粘滞(session sticky)负载均衡算法。**可以通过哈希算法,对客户端 IP 地址或者会话 ID 计算哈希值,将取得的哈希值与服务器列表的大小进行取模运算,最终得到的值就是应该被路由到的服务器编号。** 这样,我们就可以把同一个 IP 过来的所有请求,都路由到同一个后端服务器上。 +- 数据分片 +- 分布式存储:一致性哈希算法、虚拟哈希槽 + +### 典型应用场景 + +Java 的 HashMap 工具类,其 + +- HashMap 默认的初始大小是 16 +- 最大装载因子默认是 0.75,当 HashMap 中元素个数超过 0.75\*capacity(capacity 表示哈希表的容量)的时候,就会启动扩容,每次扩容都会扩容为原来的两倍大小。 +- HashMap 底层采用链表法来解决冲突。即使负载因子和散列函数设计得再合理,也免不了会出现链表过长的情况,一旦出现链表过长,则会严重影响 HashMap 的性能。在 JDK1.8 版本中,对 HashMap 做了进一步优化:引入了红黑树。当链表长度太长(默认超过 8)时,链表就转换为红黑树。我们可以利用红黑树快速增删改查的特点,提高 HashMap 的性能。当红黑树结点个数少于 8 个的时候,又会将红黑树转化为链表。因为在数据量较小的情况下,红黑树要维护平衡,比起链表来,性能上的优势并不明显。 + +## 练习 + +Leetcode 练习题: + +- [705. 设计哈希集合](https://leetcode-cn.com/problems/design-hashset/) +- [706. 设计哈希映射](https://leetcode-cn.com/problems/design-hashmap/) + +## 思考 + +1. 假设我们有 10 万条 URL 访问日志,如何按照访问次数给 URL 排序? +2. 有两个字符串数组,每个数组大约有 10 万条字符串,如何快速找出两个数组中相同的字符串? + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" new file mode 100644 index 0000000..f6af72a --- /dev/null +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" @@ -0,0 +1,104 @@ +--- +title: 跳表 +categories: + - 数据结构和算法 +tags: + - 数据结构和算法 + - 跳表 +abbrlink: 2e152a56 +date: 2020-10-23 09:21:13 +permalink: /pages/62671a/ +--- + +# 跳表 + +## 什么是跳表 + +对于一个有序数组,可以使用高效的二分查找法,其时间复杂度为 `O(log n)`。 + +但是,即使是有序的链表,也只能使用低效的顺序查找,其时间复杂度为 `O(n)`。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323113532.png) + +如何提高链表的查找效率呢? + +我们可以对链表加一层索引。具体来说,可以每两个结点提取一个结点到上一级,我们把抽出来的那一级叫作**索引**或**索引层**。索引节点中通过一个 down 指针,指向下一级结点。通过这样的改造,就可以支持类似二分查找的算法。我们把改造之后的数据结构叫作**跳表**(Skip list)。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323155309.png) + +随着数据的不断增长,一级索引层也变得越来越长。此时,我们可以为一级索引再增加一层索引层:二级索引层。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323155346.png) + +随着数据的膨胀,当二级索引层也变得很长时,我们可以继续为其添加新的索引层。**这种链表加多级索引的结构,就是跳表**。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323114408.png) + +### 跳表的时间复杂度 + +在一个具有多级索引的跳表中,第一级索引的结点个数大约就是 `n/2`,第二级索引的结点个数大约就是 `n/4`,第三级索引的结点个数大约就是 `n/8`,依次类推,也就是说,第 `k` 级索引的结点个数是第 `k-1` 级索引的结点个数的 `1/2`,那第 k 级索引结点的个数就是 `n/(2k)`。所以**跳表查询数据的时间复杂度就是 `O(logn)`**。 + +### 跳表的空间复杂度 + +比起单纯的单链表,跳表需要存储多级索引,肯定要消耗更多的存储空间。 + +假设原始链表大小为 n,那第一级索引大约有 n/2 个结点,第二级索引大约有 n/4 个结点,以此类推,每上升一级就减少一半,直到剩下 2 个结点。如果我们把每层索引的结点数写出来,就是一个等比数列。 + +``` +索引节点数 = n/2 + n/4 + n/8 … + 8 + 4 + 2 = n-2 +``` + +所以,跳表的空间复杂度是 `O(n)`。 + +跳表的存储空间其实还有压缩空间。比如,我们增加索引节点的范围,由『每两个节点抽一个上级索引节点』改为『每五个节点抽一个上级索引节点』,可以显著节省存储空间。 + +实际上,在软件开发中,我们不必太在意索引占用的额外空间。在讲数据结构和算法时,我们习惯性地把要处理的数据看成整数,但是在实际的软件开发中,原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值和几个指针,并不需要存储对象,所以当对象比索引结点大很多时,那索引占用的额外空间就可以忽略了。 + +## 跳表的操作 + +跳表是一种各方面性能都比较优秀的**动态数据结构**,可以支持快速的插入、删除、查找操作,写起来也不复杂,甚至可以替代[红黑树](https://zh.wikipedia.org/wiki/红黑树)(Red-black tree)。 + +### 高效的动态插入和删除 + +跳表不仅支持查找操作,还支持动态的插入、删除操作,而且插入、删除操作的时间复杂度也是 `O(logn)`。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323155933.png) + +- **插入操作**:对于纯粹的单链表,需要遍历每个结点,来找到插入的位置。但是,对于跳表来说,我们讲过查找某个结点的的时间复杂度是 `O(log n)`,所以这里查找某个数据应该插入的位置,方法也是类似的,时间复杂度也是 `O(log n)`。 +- **删除操作**:如果这个结点在索引中也有出现,我们除了要删除原始链表中的结点,还要删除索引中的。因为单链表中的删除操作需要拿到要删除结点的前驱结点,然后通过指针操作完成删除。所以在查找要删除的结点的时候,一定要获取前驱结点。当然,如果我们用的是双向链表,就不需要考虑这个问题了。 + +### 跳表索引动态更新 + +当我们不停地往跳表中插入数据时,如果我们不更新索引,就有可能出现某 2 个索引结点之间数据非常多的情况。极端情况下,跳表还会退化成单链表。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323161942.png) + +如红黑树、AVL 树这样的平衡二叉树,是通过左右旋的方式保持左右子树的大小平衡,而跳表是通过随机函数来维护前面提到的“平衡性”。 + +当我们往跳表中插入数据的时候,我们可以选择同时将这个数据插入到部分索引层中。如何选择加入哪些索引层呢?可以通过一个随机函数,来决定将这个结点插入到哪几级索引中,比如随机函数生成了值 K,那我们就将这个结点添加到第一级到第 K 级这 K 级索引中。 + +## 为什么需要跳表 + +跳表是一种动态数据结构,支持快速的插入、删除、查找操作,时间复杂度都是 `O(logn)`。 + +跳表的空间复杂度是 `O(n)`。不过,跳表的实现非常灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗。虽然跳表的代码实现并不简单,但是作为一种动态数据结构,比起红黑树来说,实现要简单多了。所以很多时候,我们为了代码的简单、易读,比起红黑树,我们更倾向用跳表。 + +## 跳表的应用场景 + +经典实现:Redis 的 Sorted Set、JDK 的 `ConcurrentSkipListMap` 和 `ConcurrentSkipListSet` 都是基于跳表实现。 + +为什么 Redis 要用跳表来实现有序集合,而不是红黑树? + +Redis 中的有序集合支持的核心操作主要有下面这几个: + +- 插入一个数据; +- 删除一个数据; +- 查找一个数据; +- 按照区间查找数据(比如查找值在 [100, 356] 之间的数据); +- 迭代输出有序序列。 + +其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git a/docs/data-structure/graph.md "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" similarity index 84% rename from docs/data-structure/graph.md rename to "docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" index 2661c60..44dc4d3 100644 --- a/docs/data-structure/graph.md +++ "b/docs/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" @@ -1,10 +1,22 @@ +--- +title: 图 +categories: + - 数据结构和算法 +tags: + - 数据结构和算法 + - 图 +abbrlink: ee040603 +date: 2015-03-24 15:31:13 +permalink: /pages/21529b/ +--- + # 图 在计算机科学中,一个图就是一些*顶点*的集合,这些顶点通过一系列*边*结对(连接)。顶点用圆圈表示,边就是这些圆圈之间的连线。顶点之间通过边连接。 -
+![img](https://raw.githubusercontent.com/dunwu/images/master/cs/data-structure/graph/graph.png) -## 术语 +## 什么是图 - **阶(Order)** - 图 G 中点集 V 的大小称作图 G 的阶。 - **子图(Sub-Graph)** - 当图 G'=(V',E')其中 V‘包含于 V,E’包含于 E,则 G'称作图 G=(V,E)的子图。每个图都是本身的子图。 @@ -20,6 +32,10 @@ - **轨迹(Track)** - 如果路径 P(u,v)中的顶点各不相同,则该路径称为 u 到 v 的一条轨迹。闭的轨迹称作圈(Cycle)。 - **桥(Bridge)** - 若去掉一条边,便会使得整个图不连通,该边称为[桥](https://baike.baidu.com/item/%E6%A1%A5)。 +如果图的边没有方向性,则被成为无向图。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220314093554.jpg) + ## 图的基本操作 - 创建一个图结构 - CreateGraph(G) @@ -33,3 +49,7 @@ - 插入一条边 - InsertEdge(G,v,w) - 删除一条边 - DeleteEdge(G,v,w) - 遍历图 - Traverse(G,v) + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git a/docs/@pages/archivesPage.md b/docs/@pages/archivesPage.md new file mode 100644 index 0000000..4e2d4ed --- /dev/null +++ b/docs/@pages/archivesPage.md @@ -0,0 +1,6 @@ +--- +archivesPage: true +title: 归档 +permalink: /archives/ +article: false +--- diff --git a/docs/@pages/categoriesPage.md b/docs/@pages/categoriesPage.md new file mode 100644 index 0000000..15f359b --- /dev/null +++ b/docs/@pages/categoriesPage.md @@ -0,0 +1,6 @@ +--- +categoriesPage: true +title: 分类 +permalink: /categories/ +article: false +--- diff --git a/docs/@pages/tagsPage.md b/docs/@pages/tagsPage.md new file mode 100644 index 0000000..943f890 --- /dev/null +++ b/docs/@pages/tagsPage.md @@ -0,0 +1,6 @@ +--- +tagsPage: true +title: 标签 +permalink: /tags/ +article: false +--- diff --git a/docs/README.md b/docs/README.md index ab4fd8f..2e6316d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,28 +1,185 @@ -# 算法和数据结构 +--- +home: true +heroImage: img/bg.gif +heroText: ALGORITHM-TUTORIAL +tagline: 💾 algorithm-tutorial 是一个数据结构与算法教程。 +bannerBg: none +postList: none +footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu +--- -> :dart: 所有配套源码整理归档在 [**algorithm-tutorial**](https://github.com/dunwu/algorithm-tutorial) 项目中。 +

-## 📝 知识点 + + star + -### [数据结构](data-structure/README.md) + + fork + -- [数组](data-structure/array.md) -- [栈](data-structure/stack.md) -- [队列](data-structure/queue.md) -- [链表](data-structure/list.md) -- [树](data-structure/tree/README.md) -- [图](data-structure/graph.md) -- [堆](data-structure/heap.md) -- [散列表](data-structure/hash.md) + + build + -### [算法](algorithm/README.md) + + code style + -- [查找算法](algorithm/search) -- [排序算法](algorithm/sort.md) +

-## 📚 学习资源 +

ALGORITHM-TUTORIAL

-- 书 +> 💾 algorithm-tutorial 是一个数据结构与算法教程。 +> +> 掌握数据结构与算法,你看待问题的深度,解决问题的角度就会完全不一样。 +> +> - 🔁 项目同步维护:[Github](https://github.com/dunwu/algorithm-tutorial/) | [Gitee](https://gitee.com/turnon/algorithm-tutorial/) +> - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/algorithm-tutorial/) | [Gitee Pages](http://turnon.gitee.io/algorithm-tutorial/) + +## 📖 内容 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200702071922.png) + +- 综合 +- [数据结构和算法指南](01.数据结构和算法/00.综合/01.数据结构和算法指南.md) +- [复杂度分析](01.数据结构和算法/00.综合/02.复杂度分析.md) - 关键词:**`时间复杂度`**、**`空间复杂度`**、**`大 O 表示法`**、**`复杂度量级`** +- 线性表 + - [数组和链表](01.数据结构和算法/01.线性表/01.数组和链表.md) - 关键词:**`线性表`**、**`一维数组`**、**`多维数组`**、**`随机访问`**、**`单链表`**、**`双链表`**、**`循环链表`** + - [栈和队列](01.数据结构和算法/01.线性表/02.栈和队列.md) - 关键词:**`先进后出`**、**`后进先出`**、**`循环队列`** + - [线性表的查找](01.数据结构和算法/01.线性表/11.线性表的查找.md) + - [线性表的排序](01.数据结构和算法/01.线性表/12.线性表的排序.md) +- 树 + - [树和二叉树](01.数据结构和算法/02.树/01.树和二叉树.md) + - [堆](01.数据结构和算法/02.树/02.堆.md) + - [B+树](01.数据结构和算法/02.树/03.B+树.md) + - [LSM 树](01.数据结构和算法/02.树/04.LSM树.md) + - [字典树](01.数据结构和算法/02.树/05.字典树.md) + - [红黑树](01.数据结构和算法/02.树/06.红黑树.md) +- [哈希表](01.数据结构和算法/03.哈希表.md) - 关键词:**`哈希函数`**、**`装载因子`**、**`哈希冲突`**、**`开放寻址法`**、**`拉链法`** +- [跳表](01.数据结构和算法/04.跳表.md) - 关键词:**`多级索引`** +- [图](01.数据结构和算法/05.图.md) + +## 💻 刷题 + +### 数组 + +- [三数之和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/三数之和.java) +- [两数之和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/两数之和.java) +- [二维数组](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/二维数组.java) +- [删除排序数组中的重复项](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/删除排序数组中的重复项.java) +- [加一](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/加一.java) +- [在排序数组中查找元素的第一个和最后一个位置](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/在排序数组中查找元素的第一个和最后一个位置.java) +- [在排序数组中查找数字 I](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/在排序数组中查找数字I.java) +- [存在重复元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/存在重复元素.java) +- [对角线遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/对角线遍历.java) +- [寻找数组的中心索引](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/寻找数组的中心索引.java) +- [将数组分成和相等的三个部分](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/将数组分成和相等的三个部分.java) +- [数组二分查找](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/数组二分查找.java) +- [数组拆分 1](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/数组拆分1.java) +- [旋转数组](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/旋转数组.java) +- [旋转矩阵](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/旋转矩阵.java) +- [最大连续 1 的个数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/最大连续1的个数.java) +- [杨辉三角](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/杨辉三角.java) +- [杨辉三角 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/杨辉三角2.java) +- [模拟 ArrayList1](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/模拟ArrayList1.java) +- [模拟 ArrayList2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/模拟ArrayList2.java) +- [移动零](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/移动零.java) +- [移除元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/移除元素.java) +- [至少是其他数字两倍的最大数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/至少是其他数字两倍的最大数.java) +- [螺旋矩阵](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/螺旋矩阵.java) +- [长度最小的子数组](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/长度最小的子数组.java) +- [零矩阵](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/array/零矩阵.java) + +### 链表 + +- [两数相加](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/两数相加.java) +- [二进制链表转整数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/二进制链表转整数.java) +- [删除排序链表中的重复元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/删除排序链表中的重复元素.java) +- [单链表示例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/单链表示例.java) +- [双链表示例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/双链表示例.java) +- [反转链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/反转链表.java) +- [合并 K 个排序链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/合并K个排序链表.java) +- [合并 K 个排序链表解法 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/合并K个排序链表解法2.java) +- [合并两个有序链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/合并两个有序链表.java) +- [回文链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/回文链表.java) +- [排序链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/排序链表.java) +- [环形链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/环形链表.java) +- [相交链表](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/相交链表.java) +- [移除重复节点](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/移除重复节点.java) +- [移除链表元素](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/移除链表元素.java) +- [返回倒数第 k 个节点](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/返回倒数第k个节点.java) +- [链表的中间结点](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/list/链表的中间结点.java) + +### 栈 + +- [三合一](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/三合一.java) +- [基本计算器](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/基本计算器.java) +- [最小栈](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/最小栈.java) +- [最小栈 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/最小栈2.java) +- [有效的括号](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/有效的括号.java) +- [栈排序](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/栈排序.java) +- [棒球比赛](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/棒球比赛.java) +- [比较含退格的字符串](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/比较含退格的字符串.java) +- [用栈实现队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/用栈实现队列.java) +- [用队列实现栈](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/stack/用队列实现栈.java) + +### 队列 + +- [动态扩容数组实现的队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/动态扩容数组实现的队列.java) +- [数组实现的队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/数组实现的队列.java) +- [最近的请求次数](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/最近的请求次数.java) +- [设计循环队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/设计循环队列.java) +- [链表实现的队列](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/queue/链表实现的队列.java) + +### 字符串 + +- [二进制求和](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/AddBinary.java) +- [实现 strStr()](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ImplementStrstr.java) +- [最长公共前缀](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/LongestCommonPrefix.java) +- [反转字符串](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseString.java) +- [反转字符串中的单词](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString.java) +- [反转字符串中的单词 III](https://github.com/dunwu/algorithm/blob/master/codes/data-structure/src/main/java/io/github/dunwu/ds/str/ReverseWordsInAString3.java) + +### 树 + +- [N 叉树的最大深度](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/N叉树的最大深度.java) + +#### 二叉树 + +- [二叉树中的最大路径和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树中的最大路径和.java) +- [二叉树的中序遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的中序遍历.java) +- [二叉树的前序遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的前序遍历.java) +- [二叉树的后序遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的后序遍历.java) +- [二叉树的层次遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的层次遍历.java) +- [二叉树的层次遍历 2](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的层次遍历2.java) +- [二叉树的序列化与反序列化](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的序列化与反序列化.java) +- [二叉树的所有路径](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的所有路径.java) +- [二叉树的最大深度](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的最大深度.java) +- [二叉树的最小深度](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的最小深度.java) +- [二叉树的最近公共祖先](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的最近公共祖先.java) +- [二叉树的锯齿形层次遍历](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/二叉树的锯齿形层次遍历.java) +- [从先序遍历还原二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/从先序遍历还原二叉树.java) +- [叶子相似的树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/叶子相似的树.java) +- [填充每个节点的下一个右侧节点指针](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/填充每个节点的下一个右侧节点指针.java) +- [填充每个节点的下一个右侧节点指针 II](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/填充每个节点的下一个右侧节点指针II.java) +- [对称二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/对称二叉树.java) +- [平衡二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/平衡二叉树.java) +- [相同的树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/相同的树.java) +- [翻转二叉树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/翻转二叉树.java) +- [路径总和](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/btree/路径总和.java) + +#### 二叉搜索树 + +- [二叉搜索树中的插入操作](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/二叉搜索树中的插入操作.java) +- [二叉搜索树的最近公共祖先](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/二叉搜索树的最近公共祖先.java) +- [二叉搜索树节点最小距离](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/二叉搜索树节点最小距离.java) +- [将有序数组转换为二叉搜索树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/将有序数组转换为二叉搜索树.java) +- [验证二叉搜索树](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/main/java/io/github/dunwu/algorithm/tree/bstree/验证二叉搜索树.java) + +## 📚 资料 + +- **书籍** - 刷题必备 - 《剑指 offer》 - 《编程之美》 @@ -34,17 +191,18 @@ - 《[数据结构与算法分析 : C++描述(第 4 版)](https://www.amazon.cn/gp/product/B01LDG2DSG/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01LDG2DSG&linkCode=as2&tag=vastwork-23)》 - 《[数据结构与算法分析 : C 语言描述(第 2 版)](https://www.amazon.cn/gp/product/B002WC7NGS/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B002WC7NGS&linkCode=as2&tag=vastwork-23)》 - 《[数据结构与算法分析 : Java 语言描述(第 2 版)](https://www.amazon.cn/gp/product/B01CNP0CG6/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B01CNP0CG6&linkCode=as2&tag=vastwork-23)》 - - 《[算法(第 4 版)](https://www.amazon.cn/gp/product/B009OCFQ0O/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B009OCFQ0O&linkCode=as2&tag=vastwork-23)》- 这本近千页的书只有 6 章,其中四章分别是排序,查找,图,字符串,足见介绍细致 + - 《[算法(第 4 版)](https://www.amazon.cn/gp/product/B009OCFQ0O/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B009OCFQ0O&linkCode=as2&tag=vastwork-23)》 - 算法设计 - 《[算法设计与分析基础(第 3 版)](https://www.amazon.cn/gp/product/B00S4HCQUI/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00S4HCQUI&linkCode=as2&tag=vastwork-23)》 - - 《算法引论》 - 告诉你如何创造算法 断货 - 《Algorithm Design Manual》 - 算法设计手册 红皮书 - [《算法导论》](https://www.amazon.cn/gp/product/B00AK7BYJY/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=536&creative=3200&creativeASIN=B00AK7BYJY&linkCode=as2&tag=vastwork-23) - 是一本对算法介绍比较全面的经典书籍 - 《Algorithms on Strings,Trees and Sequences》 - 《Advanced Data Structures》 - 各种诡异高级的数据结构和算法 如元胞自动机、斐波纳契堆、线段树 600 块 -- 参考链接和学习网站 +- **学习网站** + - https://github.com/TheAlgorithms/Java - https://github.com/nonstriater/Learn-Algorithms - https://github.com/trekhleb/javascript-algorithms + - https://github.com/wangzheng0822/algo - https://github.com/kdn251/interviews/blob/master/README-zh-cn.md#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84 - [July 博客](http://blog.csdn.net/v_july_v) - 《数学建模十大经典算法》 @@ -55,17 +213,18 @@ - [The-Art-Of-Programming-By-July](https://github.com/julycoding/The-Art-Of-Programming-By-July) - [微软面试 100 题](http://blog.csdn.net/column/details/ms100.html) - [程序员编程艺术](http://blog.csdn.net/v_JULY_v/article/details/6460494) -- 基本算法演示 +- **基本算法演示** - - -- 编程网站 - - [leetcode](http://leetcode.com/) - - [openjudge](http://openjudge.cn/) 开放在线程序评测平台,可以创建自己的 OJ 小组 [九度 OJ](http://ac.jobdu.com/index.php) - - 这有个[ACM 训练方案](http://www.java3z.com/cwbwebhome/article/article19/res041.html) -- 其它 +- **编程网站** + - [leetcode](http://leetcode-cn.com/) + - [openjudge](http://openjudge.cn/) +- **教程** - [高级数据结构和算法](https://www.coursera.org/learn/gaoji-shuju-jiegou/) 北大教授张铭老师在 coursera 上的课程。完成这门课之时,你将掌握多维数组、广义表、Trie 树、AVL 树、伸展树等高级数据结构,并结合内排序、外排序、检索、索引有关的算法,高效地解决现实生活中一些比较复杂的应用问题。当然 coursera 上也还有很多其它算法方面的视频课程。 - [算法设计与分析 Design and Analysis of Algorithms](https://class.coursera.org/algorithms-001/lecture) 由北大教授 Wanling Qu 在 coursera 讲授的一门算法课程。首先介绍一些与算法有关的基础知识,然后阐述经典的算法设计思想和分析技术,主要涉及的算法设计技术是:分治策略、动态规划、贪心法、回溯与分支限界等。每个视频都配有相应的讲义(pdf 文件)以便阅读和复习。 + - [算法面试通关 40 讲](https://time.geekbang.org/course/intro/100019701) + - [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) -## 🚪 传送门 +## 🚪 传送 | [技术文档归档](https://github.com/dunwu/blog) | [算法和数据结构教程系列](https://github.com/dunwu/algorithm-tutorial) | diff --git a/docs/algorithm-template.md b/docs/algorithm-template.md new file mode 100644 index 0000000..97f82b6 --- /dev/null +++ b/docs/algorithm-template.md @@ -0,0 +1,123 @@ +# 算法代码模板 + +> 算法代码模板即算法的常见套路。熟练记忆,活学活用。 + +## 递归 + +```java +public void recursion(int level, int param1, int param2, ...) { + // 递归终止条件 + if (level > MAX_LEVEL) { + // print + return; + } + + // 当前处理逻辑 + processData(level, param1, param2, ...); + + // 递归 + recursion(level + 1, param1, param2, ...); + + // 如有必要,还原状态 + reverseState(level, data); +} +``` + +## DFS + +```java +Set visited = new HashSet<>(); + +public void dfs(Node node, Set visited) { + visited.add(node); + for (Node n : node.children) { + if (!visited.contains(n)) { + dfs(n, visited); + } + } +} +``` + +## BFS + +```java +public List> bfs(Node root) { + List> list = new ArrayList<>(); + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + List levelList = new ArrayList<>(); + + int size = queue.size(); + // 遍历当前层级所有节点 + for (int i = 0; i < size; i++) { + Node n = queue.poll(); + + // 对节点 n 做逻辑处理 + levelList.add(n.val); + + // 将 n 的所有节点加入队列 + for (Node c : n.children) { + queue.offer(c); + } + } + + list.add(levelList); + } + + return list; +} +``` + +## 二分查找 + +数组的二分查找: + +```java +int left = 0, right = nums.length - 1; +while (left <= right) { + int mid = left + (right - left) / 2; // 防止数据类型溢出 + if (nums[mid] == target) { + break or return result; + } else if (nums[mid] < target) { + left = mid + 1; + } else { + right = mid - 1; + } +} +``` + +## 动态规划 + +```java +// DP 状态定义 +int[][] dp = new int[m + 1][n + 1]; + +// 初始状态 +dp[0][0] = x; +dp[0][1] = y; + +// DP 状态推导 +for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + // 方程根据实际场景推导 + dp[i][j] = max or min { dp[i - 1][j], dp[i][j - 1], ... } + } +} + +// 返回最优解 +return dp[m][n]; +``` + +## 位运算 + +记忆常用位运算公式(无他,背就完事了) + +| | 二进制表达式 | 等价表达式 | +| -------------- | ----------------------------- | ----------------------------- | +| 判断奇偶 | `x & 1 == 1`
`x & 1 == 0` | `x % 2 == 1`
`x % 2 == 0` | +| 清零最低位的 1 | `x = x & (x - 1)` | | +| 得到最低位的 1 | `x & -x` | | + + + diff --git a/docs/algorithm/README.md b/docs/algorithm/README.md deleted file mode 100644 index 901cd3d..0000000 --- a/docs/algorithm/README.md +++ /dev/null @@ -1,266 +0,0 @@ -# 算法 - -## 算法思想 - -### 穷举算法思想 - -穷举算法(ExhaustiveAttackmethod)是最简单的一种算法,其依赖于计算机的强大计算能力来穷尽每一种可能的情况,从而达到求解问题的目的。穷举算法效率并不高,但是适应于一些没有明显规律可循的场合。 - -#### 基本算法思想 - -穷举算法的基本思想就是从所有可能的情况中搜索正确的答案,其执行步骤如下: - -1. 对于一种可能的情况,计算其结果。 -2. 判断结果是否满足要求,如果不满足则执行第(1 ) 步来搜索下一个可能的情况;如果满足要求,则表示寻找到一个正确的答案。 - -> 注意:在使用穷举算法时,需要明确问题的答案的范围,这样才可以在指定范围内搜索答案。指定范围之后,就可以使用循环语句和条件判断语句逐步验证候选答案的正确性,从而得到需要的正确答案。 - -#### 经典例子 - -《孙子算经》【鸡兔同笼问题】 -今有鸡兔同笼,上有三十五头,下有九十四足,问鸡兔各几何? -(在一个笼子里关着若干只鸡和若干只兔,从上面数共有 35 个头;从下面数共有 94 只脚。问笼中鸡和兔的数量各是多少?) - -``` -int chickenRabbit(int head,int foot,int *c,int r){ -int i,j; -int tag=0;//标志是否有解 -for(i=0;i<=head;i++){//鸡的个数穷举 - j=head-i;//兔子的个数 - if(i2+j*4==foot){//判断是否满足条件 - tag=1; - *c=i; - *r=j; - } -} -return tag; -} -int main() -{ - int c,r; - if(chickenRabbit(35,94,&c,&r)==1){//如果有解输出 - printf("chickens=%d,rabbits=%d\n",c,r); - }else{//如果无解 - printf("无解\n"); - } - return 0; -} -``` - -### 递推算法思想 - -递推算法是非常常用的算法思想,在数学计算等场合有着广泛的应用。递推算法适合有明显公式规律的场合。 - -#### 基本算法思想 - -递推算法是一种理性思维模式的代表,根据已有的数据和关系,逐步推导而得到结果。递推算法的执行过程如下: -(1) 根据已知结果和关系,求解中间结果。 -(2)判定是否达到要求,如果没有达到,则继续根据已知结果和关系求解中间结果。如果满足要求,则表示寻找到一个正确的答案。 - -【注意】递推算法需要用户知道答案和问题之间的逻辑关系。在许多数学问题中,都有明确的计算公式可以遵循,因此可以采用递推算法来实现。 - -#### 经典例子 - -斐波那契《算盘书》【兔子产仔问题】 -如果一对两个月大的兔子以后每一个月都可以生一对小兔子,而一对新生的兔子出生两个月后才可以生小兔子。也就是说,1 月份出生,3 月份才可产仔。那么假定一年内没有产生兔子死亡事件,那 么 1 年后共有多少对兔子呢? - -【规律分析】 -第一个月:a(1)=1 对兔子; -第二个月:a(2)=1 对兔子; -第三个月:a(3)=1+1=2 对兔子; -第四个月:a(4)=1+2=3 对兔子; -第五个月:a(5)=2+3=5 对兔子; -…… -第 n 个月:a(n)=a(n-1)+a(n-2)对兔子; - -``` -int Fibonacci(int n) -{ -int tl,t2; -if (n==1||n==2)//第1、2个月都只有1对兔子 -{ -return 1; -}else{//采用递归 -tl=Fibonacci(n-1); -t2=Fibonacci(n-2); -return tl+t2;//计算第n个月的兔子对数 -} -} -int main() -{ - int n=12; - printf("%d个月后兔子对数:%d\n",n,Fibonacci(n)); - return 0; -} -``` - -### 递归算法思想 - -递归算法是非常常用的算法思想。使用递归算法,往往可以简化代码编写,提高程序的可读性。但是,不合适的递归会导致程序的执行效率变低。 - -#### 基本算法思想 - -递归算法就是在程序中不断反复调用自身来达到求解问题的方法。这里的重点是调用自身,这就要求待求解的问题能够分解为相同问题的一个子问题。这样 ,通过多次递归调用,便可以完成求解。 -递归调用是一个函数在它的函数体内调用它自身的函数调用方式,这种函数也称为“递归函数”。在递归函数中,主调函数又是被调函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层。 -函数的递归调用分两种情况:直接递归和间接递归。 -• 直接递归:即在函数中调用函数本身。 -• 间接递归:即间接地调用一个函数,如出如 func_a 调 用 func_b, func_b 又调用 func_a。间接递归用得不多。 - -【注意】编写递归函数时,必须使用 if 语句强制函数在未执行递归调用前返回。否则,在调用函数后,它永远不会返回,这是一个很容易犯的错误。 - -#### 经典例子 - -【阶乘】 - -``` -n!=n(n-1)(n-2)……21 -1 -long fact(int n){ -if(n<=1)return 1; -else - return nfact(n-1); -} -int main() -{ - int n=11; - printf("%d的阶乘是%d\n",n,fact(n)); - return 0; -} -``` - -### 分治算法思想 - -分治算法是一种化繁为简的算法思想。分治算法往往应用于计算步骤比较复杂的问题,通过将问题简化而逐步得到结果。 - -#### 基本算法思想 - -分治算法的基本思想是将一个计算复杂的问题分为规模较小,计算简单的小问题求解,然后综合各个小问题,得到最终问题的答案。分治算法的执行过程如下: -(1)对于一个规模为 N 的问题,若该问题可以容易地解决(比如说规模>^较小),则直接解决,否则执行下面的步骤。 -(2)将该问题分解为” 个规模较小的子问题,这些子问题互相独立,并且原问题形式相同。 -(3)递归地解子问题。 -(4)然后,将各子问题的解合并得到原问题的解。 - -【注意】使用分治算法需要待求解问题能够化简为若干个小规模的相同问题,通过逐步划分,达到一个易于求解的阶段而直接进行求解。然后,程序中可以使用递归算法来进行求解。 - -#### 经典例子 - -【寻找假币问题】 -一个袋子里有 30 个硬币,其中一枚是假币,并且假币和真币一模- 样,肉眼很难分辨,目前只知道假币比真币重量轻一点。请问如何区分出假币? - -``` -int falseCoin(int coin[],int low,int high){ -int i,sum1,sum2,sum3; -int re; -sum1=sum2=sum3=0; -//若只有两个硬币 -if(low+1==high){ - if(coin[low]sum2){//后半段轻,假币在后半段 - re=falseCoin(coin,low+(high-low)/2+1,high); - return re; - }else if(sum1sum2){//后半段轻,假币在后半段 - re=falseCoin(coin,low+(high-low)/2+1,high); - return re; - }else if(sum1 - -# Algorithm Tutorial - -> 算法和数据结构教程 - -[开始阅读](README.md) diff --git a/docs/data-structure/README.md b/docs/data-structure/README.md deleted file mode 100644 index 2ec2932..0000000 --- a/docs/data-structure/README.md +++ /dev/null @@ -1,20 +0,0 @@ -## 数据结构 - -> `数据结构` 是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。 -> -> 记为:`Data_Structure=(D,R)`。其中 D 是数据元素的集合,R 是该集合中所有元素之间的关系的有限集合。 - -## :memo: 知识点 - -- **:one: 数据结构** - - [数组](array.md) - - [栈](stack.md) - - [队列](queue.md) - - [链表](list.md) - - [树](tree) - - [树](tree/tree.md) - - [二叉树](tree/binary-tree.md) - - [红黑树](tree/red-black-tree.md) - - [图](graph.md) - - [堆](heap.md) - - [散列表](hash.md) diff --git a/docs/data-structure/array.md b/docs/data-structure/array.md deleted file mode 100644 index b571eb6..0000000 --- a/docs/data-structure/array.md +++ /dev/null @@ -1,135 +0,0 @@ -# 数组 - -> 所谓数组,是有序的元素序列。若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按无序的形式组织起来的一种形式。这些无序排列的同类数据元素的集合称为数组。 - - - -- [简介](#简介) - - [一维数组](#一维数组) - - [二维数组](#二维数组) - - [多维数组](#多维数组) - - [数组的特性](#数组的特性) -- [数组中的操作](#数组中的操作) -- [更多内容](#更多内容) - - - -## 简介 - -`数组`是一种基本的数据结构,用于按顺序`存储元素的集合`。但是元素可以随机存取,因为数组中的每个元素都可以通过数组`索引`来识别。 - -数组可以有一个或多个维度。 - -### 一维数组 - -一维(或单维)数组是一种线性数组,其中元素的访问是以行或列索引的单一下标表示。 - -这里有一个例子: - -
- -在上面的例子中,数组 A 中有 6 个元素。也就是说,A 的长度是 6 。我们可以使用 A[0] 来表示数组中的第一个元素。因此,A[0] = 6 。类似地,A[1] = 3,A[2] = 8,依此类推。 - -### 二维数组 - -类似于一维数组,二维数组也是由元素的序列组成。但是这些元素可以排列在矩形网格中而不是直线上。 - -类似于一维数组,`二维数组`也是由元素的序列组成。但是这些元素可以排列在矩形网格中而不是直线上。 - -在一些语言中,多维数组实际上是在`内部`作为一维数组实现的,而在其他一些语言中,`实际上`根本没有`多维数组`。 - -**1. C++ 将二维数组存储为一维数组。** - -下图显示了*大小为 M \* N 的数组 A* 的实际结构: - -
- -因此,如果我们将 A 定义为也包含 _M \* N_ 个元素的一维数组,那么实际上 A[i][j] 就等于 A[i * N + j]。 - -**2. 在 Java 中,二维数组实际上是包含着 M 个元素的一维数组,每个元素都是包含有 N 个整数的数组。** - -下图显示了 Java 中二维数组 A 的实际结构: - -
- -二维数组示例: - -```java -public class TwoDimensionArray { - private static void printArray(int[][] a) { - for (int i = 0; i < a.length; ++i) { - System.out.println(a[i]); - } - for (int i = 0; i < a.length; ++i) { - for (int j = 0; a[i] != null && j < a[i].length; ++j) { - System.out.print(a[i][j] + " "); - } - System.out.println(); - } - } - - public static void main(String[] args) { - System.out.println("Example I:"); - int[][] a = new int[2][5]; - printArray(a); - System.out.println("Example II:"); - int[][] b = new int[2][]; - printArray(b); - System.out.println("Example III:"); - b[0] = new int[3]; - b[1] = new int[5]; - printArray(b); - } -} -``` - -### 多维数组 - -普通数组采用一个整数来作下标。多维数组(高维数组)的概念特别是在数值计算和图形应用方面非常有用。我们在多维数组之中采用一系列有序的整数来标注,如在[ 3,1,5 ] 。这种整数列表之中整数的个数始终相同,且被称为数组的“维度”。关于每个数组维度的边界称为“维”。维度为 k 的数组通常被称为 k 维。 - -多维数组的数组名字,在表达式中自动转换为数组首元素地址值,但这个首元素实际上是去除数组下标第一维之后的数组剩余部分。 - -### 数组的特性 - -数组设计之初是在形式上依赖内存分配而成的,所以必须在使用前预先请求空间。这使得数组有以下特性: - -1. 请求空间以后大小固定,不能再改变(数据溢出问题); -2. 在内存中有空间连续性的表现,中间不会存在其他程序需要调用的数据,为此数组的专用内存空间; -3. 在旧式编程语言中(如有中阶语言之称的 C),程序不会对数组的操作做下界判断,也就有潜在的越界操作的风险(比如会把数据写在运行中程序需要调用的核心部分的内存上)。 - -因为简单数组强烈倚赖计算机硬件之内存,所以不适用于现代的程序设计。欲使用可变大小、硬件无关性的数据类型,Java 等程序设计语言均提供了更高级的数据结构:ArrayList、Vector 等动态数组。 - -## 数组中的操作 - -```java -public class Main { - public static void main(String[] args) { - // 1. Initialize - int[] a0 = new int[5]; - int[] a1 = {1, 2, 3}; - // 2. Get Length - System.out.println("The size of a1 is: " + a1.length); - // 3. Access Element - System.out.println("The first element is: " + a1[0]); - // 4. Iterate all Elements - System.out.print("[Version 1] The contents of a1 are:"); - for (int i = 0; i < a1.length; ++i) { - System.out.print(" " + a1[i]); - } - System.out.println(); - System.out.print("[Version 2] The contents of a1 are:"); - for (int item: a1) { - System.out.print(" " + item); - } - System.out.println(); - // 5. Modify Element - a1[0] = 4; - // 6. Sort - Arrays.sort(a1); - } -} -``` - -## 更多内容 - -- https://zh.wikipedia.org/wiki/数组 diff --git a/docs/data-structure/hash.md b/docs/data-structure/hash.md deleted file mode 100644 index e5fa08d..0000000 --- a/docs/data-structure/hash.md +++ /dev/null @@ -1,81 +0,0 @@ -# 哈希表 - -> 关键词: hash, 哈希表, 哈希函数 - - - -- [简介](#简介) -- [原理](#原理) -- [更多内容](#更多内容) - - - -## 简介 - ---- - -`哈希表`是一种使用`哈希函数`组织数据,以支持快速插入和搜索的数据结构。 - -有两种不同类型的哈希表:哈希集合和哈希映射。 - -- `哈希集合`是`集合`数据结构的实现之一,用于存储`非重复值`。 -- `哈希映射`是`映射` 数据结构的实现之一,用于存储`(key, value)`键值对。 - -在`标准模板库`的帮助下,哈希表是`易于使用的`。大多数常见语言(如 Java,C ++ 和 Python)都支持哈希集合和哈希映射。 - -通过选择合适的哈希函数,哈希表可以在插入和搜索方面实现`出色的性能`。 - -## 原理 - ---- - -哈希表的关键思想是使用哈希函数将键映射到存储桶。更确切地说, - -1. 当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中; -2. 当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。 - -### 哈希函数示例 - -
- -在示例中,我们使用 y = x % 5 作为哈希函数。让我们使用这个例子来完成插入和搜索策略: - -1. 插入:我们通过哈希函数解析键,将它们映射到相应的桶中。 - - 例如,1987 分配给桶 2,而 24 分配给桶 4。 -2. 搜索:我们通过相同的哈希函数解析键,并仅在特定存储桶中搜索。 - - 如果我们搜索 1987,我们将使用相同的哈希函数将 1987 映射到 2。因此我们在桶 2 中搜索,我们在那个桶中成功找到了 1987。 - - 例如,如果我们搜索 23,将映射 23 到 3,并在桶 3 中搜索。我们发现 23 不在桶 3 中,这意味着 23 不在哈希表中。 - -### 哈希表的关键 - -#### 1. 哈希函数 - -哈希函数是哈希表中最重要的组件,该哈希表用于将键映射到特定的桶。在上一节的示例中,我们使用 `y = x % 5` 作为散列函数,其中 `x` 是键值,`y` 是分配的桶的索引。 - -散列函数将取决于`键值的范围`和`桶的数量。` - -下面是一些哈希函数的示例: - -
- -哈希函数的设计是一个开放的问题。其思想是尽可能将键分配到桶中,理想情况下,完美的哈希函数将是键和桶之间的一对一映射。然而,在大多数情况下,哈希函数并不完美,它需要在桶的数量和桶的容量之间进行权衡。 - -#### 2. 冲突解决 - -理想情况下,如果我们的哈希函数是完美的一对一映射,我们将不需要处理冲突。不幸的是,在大多数情况下,冲突几乎是不可避免的。例如,在我们之前的哈希函数(_y = x % 5_)中,1987 和 2 都分配给了桶 2,这是一个`冲突`。 - -冲突解决算法应该解决以下几个问题: - -1. 如何组织在同一个桶中的值? -2. 如果为同一个桶分配了太多的值,该怎么办? -3. 如何在特定的桶中搜索目标值? - -根据我们的哈希函数,这些问题与`桶的容量`和可能映射到`同一个桶`的`键的数目`有关。 - -让我们假设存储最大键数的桶有 `N` 个键。 - -通常,如果 _N_ 是常数且很小,我们可以简单地使用一个数组将键存储在同一个桶中。如果 _N_ 是可变的或很大,我们可能需要使用`高度平衡的二叉树`来代替。 - -## 更多内容 - -https://leetcode-cn.com/explore/learn/card/hash-table/ diff --git a/docs/data-structure/heap.md b/docs/data-structure/heap.md deleted file mode 100644 index d324d2f..0000000 --- a/docs/data-structure/heap.md +++ /dev/null @@ -1,12 +0,0 @@ -# 堆 - -堆是一种特殊的基于树的满足某些特性的数据结构,整个堆中的所有父子节点的键值都会满足相同的排序条件。堆更准确地可以分为最大堆与最小堆,在最大堆中,父节点的键值永远大于或者等于子节点的值,并且整个堆中的最大值存储于根节点;而最小堆中,父节点的键值永远小于或者等于其子节点的键值,并且整个堆中的最小值存储于根节点。 - -
- -时间复杂度: - -- 访问最大值 / 最小值: `O(1)` -- 插入: `O(log(n))` -- 移除最大值 / 最小值: `O(log(n))` - diff --git a/docs/data-structure/list.md b/docs/data-structure/list.md deleted file mode 100644 index 81abf8d..0000000 --- a/docs/data-structure/list.md +++ /dev/null @@ -1,23 +0,0 @@ -# 线性表 - -## 单链表 - -单链表中的每个结点不仅包含值,还包含链接到下一个结点的`引用字段`。通过这种方式,单链表将所有结点按顺序组织起来。、 - -下面是一个单链表的例子: - -
- -蓝色箭头显示单个链接列表中的结点是如何组合在一起的。 - -与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按`索引`来`访问元素`平均要花费 `O(N)`时间,其中 N 是链表的长度。 - -## 双链表 - -双链表以类似的方式工作,但`还有一个引用字段`,称为`“prev”`字段。有了这个额外的字段,您就能够知道当前结点的前一个结点。 - -让我们看一个例子: - -
- -绿色箭头表示我们的“prev”字段是如何工作的。 diff --git a/docs/data-structure/queue.md b/docs/data-structure/queue.md deleted file mode 100644 index 72122ca..0000000 --- a/docs/data-structure/queue.md +++ /dev/null @@ -1,12 +0,0 @@ -# 队列 - -队列是元素的集合,其包含了两个基本操作:入队(enqueue) 操作可以用于将元素插入到队列中,而出队(dequeue)操作则是将元素从队列中移除。 - -遵循先入先出原则 (FIFO)。 - -时间复杂度: - -- 索引: `O(n)` -- 搜索: `O(n)` -- 插入: `O(1)` -- 移除: `O(1)` diff --git a/docs/data-structure/stack.md b/docs/data-structure/stack.md deleted file mode 100644 index ed2a62e..0000000 --- a/docs/data-structure/stack.md +++ /dev/null @@ -1,41 +0,0 @@ -# 堆栈 - -> 堆栈(英语:stack)又称为栈或堆叠,是计算机科学中一种特殊的串列形式的抽象数据类型,其特殊之处在于只能允许在链表或数组的一端(称为堆栈顶端指针,英语:top)进行加入数据(英语:push)和输出数据(英语:pop)的运算。另外堆栈也可以用一维数组或链表的形式来完成。堆栈的另外一个相对的操作方式称为队列。 -> -> 由于堆栈数据结构只允许在一端进行操作,因而按照后进先出(LIFO, Last In First Out)的原理运作。 - -
- - - -- [概念](#概念) -- [应用](#应用) -- [更多内容](#更多内容) - - - -## 概念 - -### 特点 - -堆栈的基本特点: - -1. 先入后出,后入先出。 -2. 除头尾节点之外,每个元素有一个前驱,一个后继。 - -### 操作 - -堆栈数据结构使用两种基本操作:推入(压栈,push)和弹出(弹栈,pop): - -- 推入 - 将数据放入堆栈的顶端(数组形式或串列形式),堆栈顶端 top 指针加一。 -- 弹出 - 将顶端数据数据输出(回传),堆栈顶端数据减一。 - -## 应用 - -- 回溯 -- 递归 -- 深度优先搜索 - -## 更多内容 - -- https://zh.wikipedia.org/wiki/堆栈 diff --git a/docs/data-structure/tree/README.md b/docs/data-structure/tree/README.md deleted file mode 100644 index ac90d07..0000000 --- a/docs/data-structure/tree/README.md +++ /dev/null @@ -1 +0,0 @@ -# 树 diff --git a/docs/data-structure/tree/binary-tree.md b/docs/data-structure/tree/binary-tree.md deleted file mode 100644 index 8993673..0000000 --- a/docs/data-structure/tree/binary-tree.md +++ /dev/null @@ -1,37 +0,0 @@ -# 二叉树 - -
- - - -- [简介](#简介) - - [二叉树的性质](#二叉树的性质) - - [满二叉树](#满二叉树) - - [完全二叉树](#完全二叉树) - - - -## 简介 - -二叉树是 N 个节点的有限集合,它或者是空树,或者是由一个根节点及两棵不想交的且分别称为左右子树的二叉树所组成。 - -### 二叉树的性质 - -1. 二叉树第 i 层上的结点数目最多为 **2i-1** (i≥1)。 -2. 深度为 k 的二叉树至多有 **2k-1** 个结点(k≥1)。 -3. 包含 n 个结点的二叉树的高度至少为 **log2(n+1)**。 -4. 在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1。 - -### 满二叉树 - -定义:高度为 h,并且由 **2h–1** 个结点的二叉树,被称为满二叉树。 - -
- -### 完全二叉树 - -定义:一棵二叉树中,只有最下面两层结点的度可以小于 2,并且最下一层的叶结点集中在靠左的若干位置上。这样的二叉树称为完全二叉树。 - -特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。 - -
diff --git a/docs/data-structure/tree/red-black-tree.md b/docs/data-structure/tree/red-black-tree.md deleted file mode 100644 index a75d059..0000000 --- a/docs/data-structure/tree/red-black-tree.md +++ /dev/null @@ -1,258 +0,0 @@ -# 红黑树 - -> 红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的:它可以在 $O(\log_2 N)$ 时间内做查找,插入和删除,这里的 n 是树中元素的数目。 - -## 红黑树的性质 - -红黑树,顾名思义,通过红黑两种颜色域保证树的高度近似平衡。它的每个节点是一个五元组:color(颜色),key(数据),left(左孩子),right(右孩子)和 p(父节点)。 - -红黑树的定义也是它的性质,有以下五条: - -1. 节点是红色或黑色。 - -2. 根是黑色。 - -3. 所有叶子都是黑色(叶子是 NIL 节点)。 - -4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。 - -5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。 - -
- -
- -这五个性质强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。为什么呢?性质 4 暗示着任何一个简单路径上不能有两个毗连的红色节点,这样,最短的可能路径全是黑色节点,最长的可能路径有交替的红色和黑色节点。同时根据性质 5 知道:所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。 - -## 红黑树的操作 - -因为红黑树也是二叉查找树,因此红黑树上的查找操作与普通二叉查找树上的查找操作相同。然而,红黑树上的插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的性质需要少量($O(\log_2 N)$)的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为 $O(\log_2 N)$ 次。 - -### 插入操作 - -插入操作可以概括为以下几个步骤: - -1. 查找要插入的位置,时间复杂度为:$O(N)$ - -2. 将新节点的 color 赋为红色 - -3. 自下而上重新调整该树为红黑树 - -其中,第 1 步的查找方法跟普通二叉查找树一样,第 2 步之所以将新插入的节点的颜色赋为红色,是因为:如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整,这样简单多了。下面讨论步骤 3 的一些细节: - -设要插入的节点为 N,其父节点为 P,其父节点 P 的兄弟节点为 U(即 P 和 U 是同一个节点的两个子节点)。 - -* 如果 P 是黑色的,则整棵树不必调整便是红黑树。 - -* 如果 P 是红色的(可知,其父节点 G 一定是黑色的),则插入 N 后,违背了性质 4,需要进行调整。调整时分以下 3 种情况: - - 3.1. 如果父节点 P 和叔父节点 U 二者都是红色 - -
- -
- -如上图所示,我们将 P 和 U 重绘为黑色,并重绘节点 G 为红色(用来保持性质 5)。 - -现在新节点 N 有了一个黑色的父节点 P,因为通过父节点 P 或叔父节点 U 的任何路径都必定通过祖父节点 G,在这些路径上的黑节点数目没有改变。 - -但是,红色的祖父节点 G 的父节点也有可能是红色的,这就违反了性质 4。为了解决这个问题,我们在祖父节点 G 上递归调整颜色。 - -3.2. 父节点 P 是红色而叔父节点 U 是黑色或缺少,新节点 N 是右孩子节点,而父节点 P 又是其父节点 G 的左孩子节点。 - -
- -
- -在这种情形下,我们进行一次左旋转调换新节点和其父节点的角色;接着,我们按情形 3.3 处理以前的父节点 P 以解决仍然失效的性质 4。注意这个改变会导致某些路径通过它们以前不通过的新节点 N(比如图中 1 号叶子节点)或不通过节点 P(比如图中 3 号叶子节点),但由于这两个节点都是红色的,所以性质 5 仍有效。 - -3.3. 父节点 P 是红色而叔父节点 U 是黑色或缺少,新节点 N 是左孩子节点,而父节点 P 又是其父节点 G 的左孩子节点。 - -
- -
- -在这种情形下,我们进行针对祖父节点 G 的一次右旋转;在旋转产生的树中,以前的父节点 P 现在是新节点 N 和以前的祖父节点 G 的父节点。我们知道以前的祖父节点 G 是黑色,否则父节点 P 就不可能是红色(如果 P 和 G 都是红色就违反了性质 4,所以 G 必须是黑色)。我们切换以前的父节点 P 和祖父节点 G 的颜色,结果的树满足性质 4。性质 5 也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点 G,现在它们都通过以前的父节点 P。在各自的情形下,这都是三个节点中唯一的黑色节点。 - -### 删除操作 - -删除操作可以概括为以下几个步骤: - -1. 查找要删除位置,时间复杂度为:O(N) - -2. 用删除节点后继或者节点替换该节点(只进行数据替换即可,不必调整指针,后继节点是中序遍历中紧挨着该节点的节点,即:右孩子的最左孩子节点) - -3. 如果删除节点的替换节点为黑色,则需重新调整该树为红黑树 - -其中,第 1 步的查找方法跟普通二叉查找树一样,第 2 步之所以用后继节点替换删除节点,是因为这样可以保证该后继节点之上仍是一个红黑树,而后继节点可能是一个叶节点或者只有右子树的节点,这样只需用有节点替换后继节点即可达到删除的目的。如果需要删除的节点有两个儿子,那么问题可以被转化成删除另一个只有一个儿子的节点的问题。 - -在第 3 步中 - -* 如果,如果删除节点为红色节点,则他的父亲和孩子全为黑节点,这样直接删除该节点即可,不必进行任何调整。 - -* 如果删除节点是黑节点,分四种情况: - -设要删除的节点为 N,其父节点为 P,其兄弟节点为 S。 - -由于 N 是黑色的,则 P 可能是黑色的,也可能是红色的,S 也可能是黑色的或者红色的 - -3.1 S 是红色的 - -此时 P 肯定是红色的。我们对 N 的父节点进行左旋转,然后把红色兄弟转换成 N 的祖父。我们接着对调 N 的父亲和祖父的颜色。尽管所有的路径仍然有相同数目的黑色节点,现在 N 有了一个黑色的兄弟和一个红色的父亲,所以我们可以接下去按 (2)、(3)或(4)情况来处理。 - -
- -
- -3.2 S和S的孩子全是黑色的 - -在这种情况下,P 可能是黑色的或者红色的,我们简单的重绘 S 为红色。结果是通过 S 的所有路径,它们就是以前不通过 N 的那些路径,都少了一个黑色节点。因为删除 N 的初始的父亲使通过 N 的所有路径少了一个黑色节点,这使事情都平衡了起来。但是,通过 P 的所有路径现在比不通过 P 的路径少了一个黑色节点。接下来,要调整以 P 作为 N 递归调整树。 - -
- -
- -3.3 S是黑色的,S的左孩子是红色,右孩子是黑色 - -这种情况下我们在 S 上做右旋转,这样 S 的左儿子成为 S 的父亲和 N 的新兄弟。我们接着交换 S 和它的新父亲的颜色。所有路径仍有同样数目的黑色节点,但是现在 N 有了一个右儿子是红色的黑色兄弟,所以我们进入了情况(4)。N 和它的父亲都不受这个变换的影响。 - -
- -
- -3.4 S是黑色的,S的右孩子是红色 - -在这种情况下我们在 N 的父亲上做左旋转,这样 S 成为 N 的父亲和 S 的右儿子的父亲。我们接着交换 N 的父亲和 S 的颜色,并使 S 的右儿子为黑色。子树在它的根上的仍是同样的颜色,所以属性 3 没有被违反。但是,N 现在增加了一个黑色祖先: 要么 N 的父亲变成黑色,要么它是黑色而 S 被增加为一个黑色祖父。所以,通过 N 的路径都增加了一个黑色节点。 - -
- -
- -## 示例代码 - -### 红黑树插入操作调整 - -fixAfterInsertion 方法摘自 JDK8 的 TreeMap.java。 - -阅读本示例前,请参看本文的“插入操作”一节。 - -```java - private void fixAfterInsertion(Entry x) { - // 2. 将新节点的 color 赋为红色 - x.color = RED; - - // 3. 自下而上重新调整该树为红黑树 - while (x != null && x != root && x.parent.color == RED) { // 如果父节点是黑色的,则整棵树不必调整便是红黑树。 - if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // 父节点是祖父节点的左节点 - Entry y = rightOf(parentOf(parentOf(x))); // 叔叔节点 - if (colorOf(y) == RED) { // 3.1 叔叔节点是红色的 - setColor(parentOf(x), BLACK); - setColor(y, BLACK); - setColor(parentOf(parentOf(x)), RED); - x = parentOf(parentOf(x)); - } else { - // 3.2 新节点是右孩子节点:左旋新节点和父节点;调换新节点和父节点的颜色;右旋祖父节点 - if (x == rightOf(parentOf(x))) { - x = parentOf(x); - rotateLeft(x); // 父节点左旋 - } - setColor(parentOf(x), BLACK); - setColor(parentOf(parentOf(x)), RED); - rotateRight(parentOf(parentOf(x))); - } - } else { // 父节点是祖父节点的右节点 - Entry y = leftOf(parentOf(parentOf(x))); // 叔叔节点 - if (colorOf(y) == RED) { // 3.1 叔叔节点是红色的 - setColor(parentOf(x), BLACK); - setColor(y, BLACK); - setColor(parentOf(parentOf(x)), RED); - x = parentOf(parentOf(x)); - } else { - // 新节点是左孩子节点 - if (x == leftOf(parentOf(x))) { - x = parentOf(x); - rotateRight(x); // 父节点右旋 - } - setColor(parentOf(x), BLACK); // 原父亲节点设为黑色 - setColor(parentOf(parentOf(x)), RED); // 原祖父节点设为红色 - rotateLeft(parentOf(parentOf(x))); - } - } - } - root.color = BLACK; -} -``` - -### 红黑树删除操作调整 - -fixAfterDeletion 方法摘自 JDK8 的 TreeMap.java。 - -阅读本示例前,请参看本文的“删除操作”一节。 - -```java -private void fixAfterDeletion(Entry x) { - while (x != root && colorOf(x) == BLACK) { - if (x == leftOf(parentOf(x))) { - Entry sib = rightOf(parentOf(x)); - - if (colorOf(sib) == RED) { - setColor(sib, BLACK); - setColor(parentOf(x), RED); - rotateLeft(parentOf(x)); - sib = rightOf(parentOf(x)); - } - - if (colorOf(leftOf(sib)) == BLACK && - colorOf(rightOf(sib)) == BLACK) { - setColor(sib, RED); - x = parentOf(x); - } else { - if (colorOf(rightOf(sib)) == BLACK) { - setColor(leftOf(sib), BLACK); - setColor(sib, RED); - rotateRight(sib); - sib = rightOf(parentOf(x)); - } - setColor(sib, colorOf(parentOf(x))); - setColor(parentOf(x), BLACK); - setColor(rightOf(sib), BLACK); - rotateLeft(parentOf(x)); - x = root; - } - } else { // symmetric - Entry sib = leftOf(parentOf(x)); - - if (colorOf(sib) == RED) { - setColor(sib, BLACK); - setColor(parentOf(x), RED); - rotateRight(parentOf(x)); - sib = leftOf(parentOf(x)); - } - - if (colorOf(rightOf(sib)) == BLACK && - colorOf(leftOf(sib)) == BLACK) { - setColor(sib, RED); - x = parentOf(x); - } else { - if (colorOf(leftOf(sib)) == BLACK) { - setColor(rightOf(sib), BLACK); - setColor(sib, RED); - rotateLeft(sib); - sib = leftOf(parentOf(x)); - } - setColor(sib, colorOf(parentOf(x))); - setColor(parentOf(x), BLACK); - setColor(leftOf(sib), BLACK); - rotateRight(parentOf(x)); - x = root; - } - } - } - - setColor(x, BLACK); -} -``` - -## 资料 - -https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91 diff --git a/docs/data-structure/tree/tree.md b/docs/data-structure/tree/tree.md deleted file mode 100644 index be4fa8b..0000000 --- a/docs/data-structure/tree/tree.md +++ /dev/null @@ -1,47 +0,0 @@ -# 树 - -## 概念 - -### 什么是树? - -在计算器科学中,树(英语:tree)是一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由 n(n>0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点: - -- 每个节点有零个或多个子节点; -- 没有父节点的节点称为根节点; -- 每一个非根节点有且只有一个父节点; -- 除了根节点外,每个子节点可以分为多个不相交的子树; - -
- -### 树的术语 - -- 节点的度 - 一个节点含有的子树的个数称为该节点的度; -- 树的度 - 一棵树中,最大的节点的度称为树的度; -- 叶节点或终端节点 - 度为零的节点; -- 非终端节点或分支节点 - 度不为零的节点; -- 父亲节点或父节点 - 若一个节点含有子节点,则这个节点称为其子节点的父节点; -- 孩子节点或子节点 - 一个节点含有的子树的根节点称为该节点的子节点; -- 兄弟节点 - 具有相同父节点的节点互称为兄弟节点; -- 节点的层次 - 从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推; -- 深度 - 对于任意节点 n,n 的深度为从根到 n 的唯一路径长,根的深度为 0; -- 高度 - 对于任意节点 n,n 的高度为从 n 到一片树叶的最长路径长,所有树叶的高度为 0; -- 堂兄弟节点 - 父节点在同一层的节点互为堂兄弟; -- 节点的祖先 - 从根到该节点所经分支上的所有节点; -- 子孙 - 以某节点为根的子树中任一节点都称为该节点的子孙。 -- 森林 - 由 m(m>=0)棵互不相交的树的集合称为森林; - -## 树的种类 - -- 无序树 - 树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树; -- 有序树 - 树中任意节点的子节点之间有顺序关系,这种树称为有序树; -- 二叉树 - 每个节点最多含有两个子树的树称为二叉树; -- 完全二叉树 - 对于一颗二叉树,假设其深度为 d(d>1)。除了第 d 层外,其它各层的节点数目均已达最大值,且第 d 层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树; -- 满二叉树 - 所有叶节点都在最底层的完全二叉树; -- 平衡二叉树(AVL 树) - 当且仅当任何节点的两棵子树的高度差不大于 1 的二叉树; -- 排序二叉树(二叉查找树(英语 - Binary Search Tree)) - 也称二叉搜索树、有序二叉树; -- 霍夫曼树 - 带权路径最短的二叉树称为哈夫曼树或最优二叉树; -- B 树 - 一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。 - -## 更多内容 - -- https://zh.wikipedia.org/wiki/树_(数据结构) diff --git a/docs/algorithm/search/hash-search.md b/docs/hash-search.md similarity index 93% rename from docs/algorithm/search/hash-search.md rename to docs/hash-search.md index ae736cf..84fddb5 100644 --- a/docs/algorithm/search/hash-search.md +++ b/docs/hash-search.md @@ -4,7 +4,7 @@ ### 哈希表和哈希函数 -在记录的存储位置和它的关键字之间是建立一个确定的对应关系(映射函数),使每个关键字和一个存储位置能**唯一对应**。这个映射函数称为**哈希函数**,根据这个原则建立的表称为**哈希表(Hash Table)**,也叫**散列表**。 +在记录的存储位置和它的关键字之间是建立一个确定的对应关系(映射函数),使每个关键字和一个存储位置能**唯一对应**。这个映射函数称为**哈希函数**,根据这个原则建立的表称为**哈希表(Hash Table)**,也叫**哈希表**。 以上描述,如果通过数学形式来描述就是: @@ -20,7 +20,7 @@ 构造哈希表这个场景就像汽车找停车位,如果车位被人占了,只能找空的地方停。 -
+![img](http://upload-images.jianshu.io/upload_images/3101171-4f4e0c3def86f7bb.jpg "点击查看源网页") ## 构造哈希表 @@ -81,7 +81,7 @@ 不妨设选取的p和m为13,由 f(key) = key % 13 可以得到下表。 -
+![img](https://upload-images.jianshu.io/upload_images/3101171-06a789e7f9b31da6.png) 需要注意的是,在上图中有两个关键字的探查次数为 2 ,其他都是1。 @@ -105,7 +105,7 @@ b. 35 % 13 结果是 9,而它的前面有个 9,9 % 13也是 9,存在冲突 如果对开放定址法示例中提到的序列使用拉链法,得到的结果如下图所示: -
+![img](https://upload-images.jianshu.io/upload_images/3101171-c14e03882e8a0f3a.png) ## 实现一个哈希表 diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 5eabba3..0000000 --- a/docs/index.html +++ /dev/null @@ -1,218 +0,0 @@ - - - - - algorithm-tutorial - - - - - - - - - - - -
正在加载...
- - - - - - - - - - - - - diff --git a/docs/navbar.md b/docs/navbar.md deleted file mode 100644 index 8e5c76a..0000000 --- a/docs/navbar.md +++ /dev/null @@ -1,12 +0,0 @@ -- [**:one: 数据结构**](data-structure/README.md) - - [数组](data-structure/array.md) - - [栈](data-structure/stack.md) - - [队列](data-structure/queue.md) - - [链表](data-structure/list.md) - - [树](data-structure/tree/README.md) - - [图](data-structure/graph.md) - - [堆](data-structure/heap.md) - - [散列表](data-structure/hash.md) -- [**:two: 算法**](algorithm/README.md) - - [查找算法](algorithm/search) - - [排序算法](algorithm/sort.md) diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index b317a77..0000000 --- a/docs/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "algorithm-tutorial", - "version": "1.0.0", - "scripts": { - "start": "docsify serve ./ --port 4000" - }, - "dependencies": {}, - "devDependencies": {} -} diff --git "a/docs/\347\256\227\346\263\225\346\200\235\350\267\257.md" "b/docs/\347\256\227\346\263\225\346\200\235\350\267\257.md" new file mode 100644 index 0000000..8cec531 --- /dev/null +++ "b/docs/\347\256\227\346\263\225\346\200\235\350\267\257.md" @@ -0,0 +1,114 @@ +# 算法思路 + +## 递归 + +### 使用递归的条件 + +递归需要满足的三个条件 + +- **一个问题的解可以分解为几个子问题的解** +- **这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样** +- **存在递归终止条件** + +### 递归代码要警惕堆栈溢出 + +函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。系统栈或者虚拟机栈空间一般都不大。如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。 + +那么,如何避免出现堆栈溢出呢? + +我们可以通过在代码中限制递归调用的最大深度的方式来解决这个问题 + +## 贪心算法 + +### 贪心算法思路 + +贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择,就能得到问题的答案。贪心算法需要充分挖掘题目中条件,没有固定的模式,解决有贪心算法需要一定的直觉和经验。 + +贪心算法**不是对所有问题都能得到整体最优解**。能使用贪心算法解决的问题具有「贪心选择性质」。「贪心选择性质」严格意义上需要数学证明。能使用贪心算法解决的问题必须具备「无后效性」,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。 + +### 贪心算法的应用 + +霍夫曼编码(Huffman Coding) + +Prim 和 Kruskal 最小生成树算法 + +Dijkstra 单源最短路径算法 + +## 分治算法 + +分治算法的核心就是分而治之,也就是将原问题划分成 n 个规模较小,并且结构与原问题相似的子问题,分别解决这些子问题,然后再合并其结果,得到原问题的解。 + +**分治算法是一种处理问题的思想,递归是一种编程技巧**。分治算法一般都比较适合用递归来实现。分治算法的递归实现中,每一层递归都会涉及这样三个操作: + +- 分解:将原问题分解成一系列子问题; +- 解决:递归地求解各个子问题,若子问题足够小,则直接求解; +- 合并:将子问题的结果合并成原问题。 + +分治算法能解决的问题,一般需要满足下面这几个条件: + +- 原问题与分解成的小问题具有相同的模式; +- 原问题分解成的子问题可以独立求解,子问题之间没有相关性,这一点是分治算法跟动态规划的明显区别,等我们讲到动态规划的时候,会详细对比这两种算法; +- 具有分解终止条件,也就是说,当问题足够小时,可以直接求解; +- 可以将子问题合并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减小算法总体复杂度的效果了。 + +## 回溯算法 + +### 回溯算法思路 + +**回溯法** 采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况: + +- 找到一个可能存在的正确的答案; + +- 在尝试了所有可能的分步方法后宣告该问题没有答案。 + + + +**解决一个回溯问题,实际上就是一个决策树的遍历过程**。 + +- **路径**:也就是已经做出的选择。 +- **选择列表**:也就是你当前可以做的选择。 +- **结束条件**:也就是到达决策树底层,无法再做选择的条件。 + +回溯算法的骨架: + +```text +result = [] +def backtrack(路径, 选择列表): + if 满足结束条件: + result.add(路径) + return + + for 选择 in 选择列表: + 做选择 + backtrack(路径, 选择列表) + 撤销选择 +``` + +**其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」** + +### 回溯算法应用 + +回溯算法典型问题: + +- [46. 全排列(中等)](https://leetcode-cn.com/problems/permutations/) +- [47. 全排列 II(中等)](https://leetcode-cn.com/problems/permutations-ii/) +- [N 皇后(困难)](https://leetcode-cn.com/problems/n-queens/) +37. [解数独(困难)](https://leetcode-cn.com/problems/sudoku-solver/) + +> [知乎:回溯算法套路详解 - labuladong的文章](https://zhuanlan.zhihu.com/p/93530380) + +## 动态规划 + +### 动态规划思路 + +动态规划比较适合用来求解最优问题,比如求最大值、最小值等等。 + +动态规划的应用 + +买卖股票的最佳时机 + +## 参考资料 + +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) +- 回溯 +- [知乎:回溯算法套路详解 - labuladong的文章](https://zhuanlan.zhihu.com/p/93530380) diff --git "a/docs/\347\256\227\346\263\225\347\273\203\344\271\240-\346\240\221.md" "b/docs/\347\256\227\346\263\225\347\273\203\344\271\240-\346\240\221.md" new file mode 100644 index 0000000..30e2d91 --- /dev/null +++ "b/docs/\347\256\227\346\263\225\347\273\203\344\271\240-\346\240\221.md" @@ -0,0 +1,42 @@ +# 数据结构 - 树 + +## 二叉树经典题 + +### 深度优先搜索(DFS) + +在这个策略中,我们采用 深度 作为优先级,以便从跟开始一直到达某个确定的叶子,然后再返回到达另一个分支。深度优先搜索策略又可以根据根节点、左孩子和右孩子的相对顺序被细分为**先序遍历**,**中序遍历**和**后序遍历**。 + +- [二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal) +- [二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal) +- [二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal) + +### 宽度优先搜索(BFS) + +我们按照高度顺序一层一层的访问整棵树,高层次的节点将会比低层次的节点先被访问到。 + +- [二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal) + +### 二叉树和递归 + +- [二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree) +- [对称二叉树](https://leetcode-cn.com/problems/symmetric-tree) +- [路径总和](https://leetcode-cn.com/problems/path-sum) + +### 其他 + +- [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) +- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) +- [ ] [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) +- [ ] [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) +- [ ] [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) +- [ ] [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) +- [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) +- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) +- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) + +## 二叉搜索树经典题 + +- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) +- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) +- [ ] [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) +- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) diff --git a/images/tree/red-black-tree-01.png b/images/tree/red-black-tree-01.png deleted file mode 100644 index 05f6c69..0000000 Binary files a/images/tree/red-black-tree-01.png and /dev/null differ diff --git a/images/tree/red-black-tree-delete-01.png b/images/tree/red-black-tree-delete-01.png deleted file mode 100644 index e296cc0..0000000 Binary files a/images/tree/red-black-tree-delete-01.png and /dev/null differ diff --git a/images/tree/red-black-tree-delete-02.png b/images/tree/red-black-tree-delete-02.png deleted file mode 100644 index 05a2b84..0000000 Binary files a/images/tree/red-black-tree-delete-02.png and /dev/null differ diff --git a/images/tree/red-black-tree-delete-03.png b/images/tree/red-black-tree-delete-03.png deleted file mode 100644 index 635a26f..0000000 Binary files a/images/tree/red-black-tree-delete-03.png and /dev/null differ diff --git a/images/tree/red-black-tree-delete-04.png b/images/tree/red-black-tree-delete-04.png deleted file mode 100644 index 5ec7a3a..0000000 Binary files a/images/tree/red-black-tree-delete-04.png and /dev/null differ diff --git a/images/tree/red-black-tree-insert-01.png b/images/tree/red-black-tree-insert-01.png deleted file mode 100644 index 19921d7..0000000 Binary files a/images/tree/red-black-tree-insert-01.png and /dev/null differ diff --git a/images/tree/red-black-tree-insert-02.png b/images/tree/red-black-tree-insert-02.png deleted file mode 100644 index 02ac8d6..0000000 Binary files a/images/tree/red-black-tree-insert-02.png and /dev/null differ diff --git a/images/tree/red-black-tree-insert-03.png b/images/tree/red-black-tree-insert-03.png deleted file mode 100644 index db2fd19..0000000 Binary files a/images/tree/red-black-tree-insert-03.png and /dev/null differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..47ccf68 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "algorithm-tutorial", + "version": "1.0.0", + "private": true, + "scripts": { + "clean": "rimraf docs/.temp", + "start": "vuepress dev docs", + "build": "vuepress build docs", + "deploy": "bash scripts/deploy.sh", + "editFm": "node utils/editFrontmatter.js", + "baiduPush": "node utils/baiduPush.js https://xugaoyi.com && bash baiduPush.sh", + "publish": "cd ./vdoing && npm publish && cd .. && yarn updateTheme", + "updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D", + "lint": "markdownlint -r markdownlint-rule-emphasis-style -c docs/.markdownlint.json **/*.md -i node_modules", + "lint:fix": "markdownlint -f -r markdownlint-rule-emphasis-style -c docs/.markdownlint.json **/*.md -i node_modules", + "show-help": "vuepress --help", + "view-info": "vuepress view-info ./ --temp docs/.temp" + }, + "devDependencies": { + "dayjs": "^1.9.7", + "inquirer": "^7.1.0", + "json2yaml": "^1.1.0", + "vuepress": "1.9.5", + "vuepress-plugin-baidu-autopush": "^1.0.1", + "vuepress-plugin-baidu-tongji": "^1.0.1", + "vuepress-plugin-comment": "^0.7.3", + "vuepress-plugin-demo-block": "^0.7.2", + "vuepress-plugin-fulltext-search": "^2.2.1", + "vuepress-plugin-one-click-copy": "^1.0.2", + "vuepress-plugin-thirdparty-search": "^1.0.2", + "vuepress-plugin-zooming": "^1.1.7", + "vuepress-theme-vdoing": "^1.11.2", + "yamljs": "^0.3.0", + "markdownlint-cli": "^0.25.0", + "markdownlint-rule-emphasis-style": "^1.0.1", + "rimraf": "^3.0.1", + "vue-toasted": "^1.1.25" + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9c66df2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + io.github.dunwu + algorithm-tutorial + 1.0.0 + pom + ALGORITHM-TUTORIAL + algorithm-tutorial 示例源码 + + + codes/algorithm + + diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..eb6bb1f --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,7 @@ +/** + * @see https://prettier.io/docs/en/options.html + * @see https://prettier.io/docs/en/configuration.html + */ +module.exports = { + tabWidth: 2, semi: false, singleQuote: true +} diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..a89fb1a --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env sh + +# ------------------------------------------------------------------------------ +# gh-pages 部署脚本 +# @author Zhang Peng +# @since 2020/2/10 +# ------------------------------------------------------------------------------ + +# 装载其它库 +ROOT_DIR=$( + cd $(dirname $0)/.. + pwd +) + +# 确保脚本抛出遇到的错误 +set -e + +# 生成静态文件 +npm run build + +# 进入生成的文件夹 +cd ${ROOT_DIR}/docs/.temp + +# 如果是发布到自定义域名 +# echo 'www.example.com' > CNAME + +if [[ ${GITHUB_TOKEN} && ${GITEE_TOKEN} ]]; then + msg='自动部署' + GITHUB_URL=https://dunwu:${GITHUB_TOKEN}@github.com/dunwu/algorithm-tutorial.git + GITEE_URL=https://turnon:${GITEE_TOKEN}@gitee.com/turnon/algorithm-tutorial.git + git config --global user.name "dunwu" + git config --global user.email "forbreak@163.com" +else + msg='手动部署' + GITHUB_URL=git@github.com:dunwu/algorithm-tutorial.git + GITEE_URL=git@gitee.com:turnon/algorithm-tutorial.git +fi +git init +git add -A +git commit -m "${msg}" +# 推送到github gh-pages分支 +git push -f "${GITHUB_URL}" master:gh-pages +git push -f "${GITEE_URL}" master:gh-pages + +cd - +rm -rf ${ROOT_DIR}/docs/.temp diff --git a/utils/config.yml b/utils/config.yml new file mode 100644 index 0000000..6fac6a2 --- /dev/null +++ b/utils/config.yml @@ -0,0 +1,14 @@ +#批量添加和修改、删除front matter配置文件 + +# 需要批量处理的路径,docs文件夹内的文件夹 (数组。映射路径:docs/arr[0]/arr[1] ... ) +path: + - docs # 第一个成员必须是docs + +# 要删除的字段 (数组) +delete: + # - test + # - tags + + # 要添加、修改front matter的数据 (front matter中没有的数据则添加,已有的数据则覆盖) +data: + article: false \ No newline at end of file diff --git a/utils/editFrontmatter.js b/utils/editFrontmatter.js new file mode 100644 index 0000000..8c223f4 --- /dev/null +++ b/utils/editFrontmatter.js @@ -0,0 +1,92 @@ +/** + * 批量添加和修改front matter ,需要配置 ./config.yml 文件。 + */ +const fs = require('fs'); // 文件模块 +const path = require('path'); // 路径模块 +const matter = require('gray-matter'); // front matter解析器 https://github.com/jonschlinkert/gray-matter +const jsonToYaml = require('json2yaml') +const yamlToJs = require('yamljs') +const inquirer = require('inquirer') // 命令行操作 +const chalk = require('chalk') // 命令行打印美化 +const readFileList = require('./modules/readFileList'); +const { type, repairDate} = require('./modules/fn'); +const log = console.log + +const configPath = path.join(__dirname, 'config.yml') // 配置文件的路径 + +main(); + +/** + * 主体函数 + */ +async function main() { + + const promptList = [{ + type: "confirm", + message: chalk.yellow('批量操作frontmatter有修改数据的风险,确定要继续吗?'), + name: "edit", + }]; + let edit = true; + + await inquirer.prompt(promptList).then(answers => { + edit = answers.edit + }) + + if(!edit) { // 退出操作 + return + } + + const config = yamlToJs.load(configPath) // 解析配置文件的数据转为js对象 + + if (type(config.path) !== 'array') { + log(chalk.red('路径配置有误,path字段应该是一个数组')) + return + } + + if (config.path[0] !== 'docs') { + log(chalk.red("路径配置有误,path数组的第一个成员必须是'docs'")) + return + } + + const filePath = path.join(__dirname, '..', ...config.path); // 要批量修改的文件路径 + const files = readFileList(filePath); // 读取所有md文件数据 + + files.forEach(file => { + let dataStr = fs.readFileSync(file.filePath, 'utf8');// 读取每个md文件的内容 + const fileMatterObj = matter(dataStr) // 解析md文件的front Matter。 fileMatterObj => {content:'剔除frontmatter后的文件内容字符串', data:{}, ...} + let matterData = fileMatterObj.data; // 得到md文件的front Matter + + let mark = false + // 删除操作 + if (config.delete) { + if( type(config.delete) !== 'array' ) { + log(chalk.yellow('未能完成删除操作,delete字段的值应该是一个数组!')) + } else { + config.delete.forEach(item => { + if (matterData[item]) { + delete matterData[item] + mark = true + } + }) + + } + } + + // 添加、修改操作 + if (type(config.data) === 'object') { + Object.assign(matterData, config.data) // 将配置数据合并到front Matter对象 + mark = true + } + + // 有操作时才继续 + if (mark) { + if(matterData.date && type(matterData.date) === 'date') { + matterData.date = repairDate(matterData.date) // 修复时间格式 + } + const newData = jsonToYaml.stringify(matterData).replace(/\n\s{2}/g,"\n").replace(/"/g,"") + '---\r\n' + fileMatterObj.content; + fs.writeFileSync(file.filePath, newData); // 写入 + log(chalk.green(`update frontmatter:${file.filePath} `)) + } + + }) +} diff --git a/utils/modules/fn.js b/utils/modules/fn.js new file mode 100644 index 0000000..48cbbd1 --- /dev/null +++ b/utils/modules/fn.js @@ -0,0 +1,21 @@ +// 类型判断 +exports.type = function (o){ + var s = Object.prototype.toString.call(o) + return s.match(/\[object (.*?)\]/)[1].toLowerCase() +} + + // 修复date时区格式的问题 + exports.repairDate = function (date) { + date = new Date(date); + return `${date.getUTCFullYear()}-${zero(date.getUTCMonth()+1)}-${zero(date.getUTCDate())} ${zero(date.getUTCHours())}:${zero(date.getUTCMinutes())}:${zero(date.getUTCSeconds())}`; +} + +// 日期的格式 +exports.dateFormat = function (date) { + return `${date.getFullYear()}-${zero(date.getMonth()+1)}-${zero(date.getDate())} ${zero(date.getHours())}:${zero(date.getMinutes())}:${zero(date.getSeconds())}` +} + +// 小于10补0 +function zero(d){ + return d.toString().padStart(2,'0') +} \ No newline at end of file diff --git a/utils/modules/readFileList.js b/utils/modules/readFileList.js new file mode 100644 index 0000000..8eb97c6 --- /dev/null +++ b/utils/modules/readFileList.js @@ -0,0 +1,43 @@ +/** + * 读取所有md文件数据 + */ +const fs = require('fs'); // 文件模块 +const path = require('path'); // 路径模块 +const docsRoot = path.join(__dirname, '..', '..', 'docs'); // docs文件路径 + +function readFileList(dir = docsRoot, filesList = []) { + const files = fs.readdirSync(dir); + files.forEach( (item, index) => { + let filePath = path.join(dir, item); + const stat = fs.statSync(filePath); + if (stat.isDirectory() && item !== '.vuepress') { + readFileList(path.join(dir, item), filesList); //递归读取文件 + } else { + if(path.basename(dir) !== 'docs'){ // 过滤docs目录级下的文件 + + const fileNameArr = path.basename(filePath).split('.') + let name = null, type = null; + if (fileNameArr.length === 2) { // 没有序号的文件 + name = fileNameArr[0] + type = fileNameArr[1] + } else if (fileNameArr.length === 3) { // 有序号的文件 + name = fileNameArr[1] + type = fileNameArr[2] + } else { // 超过两个‘.’的 + log(chalk.yellow(`warning: 该文件 "${filePath}" 没有按照约定命名,将忽略生成相应数据。`)) + return + } + if(type === 'md'){ // 过滤非md文件 + filesList.push({ + name, + filePath + }); + } + + } + } + }); + return filesList; +} + +module.exports = readFileList; \ No newline at end of file